関数のメモ2 : JavaScript

Pocket

休み中に『パーフェクト JavaScript』を読んだのでJavaScriptの関数についてメモしておく。

JavaScriptの関数

JavaScriptの関数はオブジェクト。関数はオブジェクトなのでプロパティの追加や変数への代入などオブジェクトに対する操作ができる。以後関数がオブジェクトであることを強調するときは関数を関数オブジェクトと書く。関数オブジェクトの特徴を『パーフェクト JavaScript』(p32)では下記のように説明している。

オブジェクトのすべてが関数という意味ではありません。関数は実行可能なコードを持ち、呼び出し可能という意味で(それなりに特別な)オブジェクトです。

また『JavaScript: The Good Parts』(p3)は下記のように説明している。

JavaScriptの関数は、(大部分は)レキシカルスコープを持つファーストクラスオブジェクトである。

prototypeプロパティ(explicit prototype property)と暗黙リンク(implicit prototype link)

関数オブジェクトのprototypeプロパティ(explicit prototype property)は関数オブジェクトの機能を拡張する。また関数オブジェクトはFunction.prototypeへの暗黙リンク(implicit prototype link)を持つ[1]。暗黙リンクを__proto__プロパティとして公開している実装もある。

関数オブジェクトのprototypeプロパティはインスタンス[2]に共通の機能を提供する。インスタンスの暗黙リンクは生成に使われた関数オブジェクトのpototypeプロパティを参照する。インスタンスの生成に使う関数オブジェクトをコンストラクタと呼ぶ[3]。つまり生成されたインスタンスの暗黙リンクはコンストラクタのprototypeを参照する。

プロトタイプチェーン

プロトタイプチェーンのまとめ。

  1. インスタンはコンストラクタのprototyepへ暗黙のリンクを持つ。
  2. 関数オブジェクトはFunction.prototypeへの暗黙のリンクを持つ。
  3. 1,2以外のオブジェクトはObject.prototypeへ暗黙のリンクを持つ(prototypeプロパティもオブジェクトなのでObject.prototypeへ暗黙リンクを持つ)。
// コンストラクタ
function Foo(value) {
    this.value = value;
}
// Fooを拡張
Foo.prototype.showValue = function () {
    console.log(this.value);
}
// インスタンス生成
var ins1 = new Foo('ins1');
var ins2 = new Foo('ins2');

// インスタンスのプロトタイプチェーン
console.log(ins1.__proto__ == Foo.prototype);    // ture
console.log(ins1.__proto__.__proto__ == Object.prototype); // ture

// 関数オブジェクト(Foo)のプロトタイプチェーン
console.log(Foo.__proto__ == Function.prototype);
console.log(Foo.__proto__.__proto__ == Object.prototype);

» プロトタイプチェーンの図

プロトタイプベース

JavaScriptはオブジェクトの共通したの機能・振る舞いを(型)をJavaのようにクラスやインターフェースではなくpototypeを使って実現する。またプロトタイプオブジェクトはコンストラクタ自体ではなくそのprototypeオブジェクトを指す[4]

[1] 暗黙リンクによりFunction.prototype.apply,Function.prototype.callといったFunction.prototypeオブジェクトのメソッドをすべての関数オブジェクトで利用できる
逆にすべての関数オブジェクトで利用する機能を追加する場合はFunction.prototypeを拡張すればよい(定義済みオブジェクトの変更は注意点も多い)。
[2],[3] 本記事はコンストラクタから生成したオブジェクト(関数オブジェクトではない)をインスタンスと書く。
関数オブジェクトはどのような用途で使われるのかを見た目からは判別できない。 そのためにコンストラクタとして利用されるのに備えてprototype.constructorプロパティに自身を設定する。つまり関数オブジェクトのprototypeプロパティはデフォルトでconstructorプロパティを持ち値に関数オブジェクト自身が設定されている。
» constructor : JavaScript
[4] 『パーフェクト JavaScript』に分かり易い説明がある(p36)。

関数定義

関数定義には関数宣言文と関数リテラルがある。関数宣言文も関数リテラルも下記のようにほとんど同じ文法をもつ。

// 関数宣言文
function 関数名(引数) {
    // 処理
};
// 関数リテラル
function 関数名(引数) {
    // 処理
}
または
function(引数) {
    // 処理
}

関数リテラルでは関数名が省略できることを除いて形式的には関数宣言文も関数リテラルも同じ。働きもほとんど同じ。ただし関数宣言文と関数リテラルには下記のような違いがある。

  1. 関数リテラルは関数名を省略できる。
  2. 関数リテラルは式なので値(関数オブジェクトへの参照)を返す。
  3. 巻き上げ処理の違い。
  4. 関数リテラルは行頭に書くことはできない(関数宣言文になる)。

関数リテラルは値を返す

関数宣言文なのか関数リテラルなのかは関数定義だけからでは判断がつかない場合がある。しかし前後の文脈で判断できる。

var foo = function _foo() {
    // 処理
}
// または
var foo = function() {
    // 処理
}

値を返す必要があるので関数リテラル。

function foo() {
    // 処理
}

行頭に書いてあるので(本人が関数リテラルのつもりでも)関数リテラル関数宣言文。

(function foo() {
    // 処理
})

括弧()の中は値を返す必要があるので(本人が関数宣言文のつもりでも)関数リテラル。

関数宣言文と関数リテラルの違い -巻き上げ処理-[1]

関数宣言文で定義した関数は宣言した位置に関わらず実行時にスコープの一番先頭に移動される。

foo(); // 正常に処理
bar(); // エラー発生
	
// 関数宣言文
function foo () {
    // 処理
};
// 関数リテラル
var bar = function () {
    // 処理
}

巻き上げ処理の例2。

/* エラーは発生しない */
console.log(foo());
function foo() {
    return bar();
}
function bar() {
    return 'Hello World';
}

追記 2012.02.05 変数も巻き上げ処理される。変数と関数の巻き上げ処理のまとめ。

// 巻き上げ処理
//     変数
//         変数は巻き上げ処理がされる。
//     関数
//         関数宣言文は巻き上げ処理がされる。
//         関数宣言式は巻き上げ処理されない。

// 「変数」の巻き上げ処理が行われる。
//  関数宣言文は同時に関数の巻き上げ処理も行われる
// 値はundefined
// foo is not defined, bar is not definedではない
console.log(foo); // foo(value)
console.log(bar); // undefined * bar is not definedではない
// 関数宣言文は「関数」の巻き上げ処理が行われる。
console.log(foo('Foo')); // Foo
// 関数宣言式は「関数」の巻き上げ処理が行われない。
// bar(); // bar is not a function  
     
// 関数宣言文
function foo (value) {
    return value;
};
// 関数リテラル
var bar = function (value) {
    return value
}

[1]『JavaScript: The Good Parts』(p132)では「「巻き上げ」」と記載している。

関数の実行

関数の呼び出しはドット演算子.やブラケット演算子[]を使って呼び出す[1]

var hoge = fucntion () {
    // 処理
}
window.hoge();
window['hoge']();

即時実行

即時実行はグローバル変数を使わなくてすむというメリットがある。

// 行頭にfunctionを書くと必ず関数宣言文と解釈される。
// 関数宣言文は値を返さないので下記は(即時)実行されない
function myAlert(message) {
    console.log(message);
}('Foo'); // 実行されない

// 上記を避けるために()で囲む
// (function myAlert() {}())
// ()で囲むと値を返す必要があるので関数リテラルになる。
// 関数リテラルの返す値は関数オブジェクト参照
(function myAlert(message) {
    console.log(message);
}('Foo')); // 実行結果 Foo

// 関数リテラルは関数名を省略できる。
// デバック以外にmyAlertを付ける意味は無いので無名関数で書くのが普通。
(function(message) {
    console.log(message);
}('Bar')); // 実行結果 Bar

// 関数リテラルは関数オブジェクト参照を返すので下記のように書いても実行できる。
(function(message) {
    console.log(message);
})('Bar');

即時実行で関数全体を括弧()で囲む理由は下記に詳しい解説がある。
» (function(){})() と function(){}() – IT戦記
» 紫ログ:Scheme脳で考える!! JavaScriptの関数とかクロージャとか – livedoor Blog(ブログ)

関数のプロパティ

関数はプロパティを設定できる。プロパティは下記のように分類できる。

  • クラスプロパティ( コンストラクタプロパティ,クラスオブジェクトプロパティ)
    デフォルトではlengthプロパティ(仮引数の数)とprototypeプロパティ(拡張)がある。
  • インスタンスプロトタイププロパティ
  • インスタンスプロパティ

» プロパティ : JavaScript

クロージャ

» クロージャのメモ : JavaScript

再帰

» 再帰 : JavaScript

関数とthis

関数を呼び出すさい引数のほかにthisとargumentsというプロパティが渡される。
関数がメソッドの場合のthisはメソッドを定義しているオブジェクトを指す。
イベントハンドラ・イベントリスナーの場合はそれらを設定した要素を指す。

» 関数の呼び出しのthis : JavaScript

apply・callメソッド

すべての関数はプロトタイプチェーンでFunction.prototypeに暗黙リンクを持つ。
Function.prototypeのメソッドは全ての関数で利用できる。ここではapply・callメソッドについて書く。

Function.prototype.apply (thisArg, argArray)
Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )

p118 『ECMAScript Language Specification』 5th Edition / December 2009 Ecma International 2009
»Standard ECMA-262

applayはthisArgで渡したオブジェクトをthisに設定して配列argArrayを引数として関数を実行する。callの場合は第2引数が配列ではなくスカラーになる。

function fall (x) {
    this.a = 9.8;
    return -0.5 * this.a * Math.pow(x,2) + this.height
}

var fuji = {
    height: 3776    
}
var everest = {
    height: 8848
}

fall.apply(fuji, [10]); // 10秒後の位置 実行結果 3286
fall.apply(everest,[10]); // 10秒後の位置 実行結果 8358

» Function.apply, Function.call : JavaScript

Javaのextendsにあたる明示的な継承手段をJavaScriptは提供していない。しかし実質的に継承に相当する機能を実現する方法がいくつかある。 applyを使って親オブジェクトのthisプロパティーを継承することができる。
» 継承 : JavaScript

パーフェクトJavaScript (PERFECT SERIES 4)
JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス

コメント

No comments yet.

コメントの投稿

改行と段落タグは自動で挿入されます。
メールアドレスは表示されません。