applyメソッドの動作
まずはapplyメソッドの基本動作から。baz()関数を作成して、関数内でthisを参照してみます。
'use strict'; var obj = {foo: 'foo value'}; function baz() { console.log(this); } baz.apply(obj);
consoleで確認すると、applyメソッドの引数で渡したオブジェクトがthisの参照元になっているのがわかります。(applyメソッドがないとundefined
が返ってきます。当たり前ですけど。)
thisの値をオブジェクトobjにバインドしてると言ったほうがわかりやすいかも。
Object {foo: "foo value"} foo : "foo value"
渡すオブジェクトの中に関数を定義して、その関数を呼び出してみます。
'use strict'; var obj = { foo: 'obj foo value', bar: function(){ console.log('method call.' + this.foo) } }; function baz() { console.log(this); } obj.bar.apply(obj);
結果は、method call.obj foo value
とコンソールに出力されているので、thisの参照先はobjであると言えます。つまりはapplyメソッドの第一引数に渡すオブジェクトにthisの値をバインドするので、別のオブジェクトを渡すことでそのオブジェクトにオブジェクトobjと同じ振る舞いをさせることができます。
'use strict'; var obj = { foo: 'obj foo value', bar: function(){ console.log('method call.' + this.foo); } }; var obj2 = { foo: 'obj2 foo value' }; function baz() { console.log(this); } obj.bar.apply(obj2);
結果は、method call.obj2 foo value
とobj2で定義した値が表示されたので、thisの参照先がobj2であると言えます。オブジェクトobjがレシーバの役目になっている(レシーバオブジェクト)ことがわかります。とても便利。
callメソッドの動作
次にcallメソッドですが、applyメソッドとほぼ同じ動作をします。試しに、先程のプログラムのapplyメソッドをcallメソッドに書き換えて実行してみます。
'use strict'; var obj = { foo: 'obj foo value', bar: function(){ console.log('method call.' + this.foo); } }; var obj2 = { foo: 'obj2 foo value' }; function baz() { console.log(this); } obj.bar.call(obj2);
結果は、method call.obj2 foo value
と表示されました。applyメソッドと同じ動作をしたということになります。あれ?じゃあcallメソッドってapplyメソッドとどこが違うの?という話になりますので、続いて2つのメソッドに違いについて。
applyメソッドとcallメソッドの違い
それでは2つのメソッドの違いを見ていきます。
引数の違い
applyメソッドとcallメソッドの第一引数にオブジェクトを渡す点は共通ですが、第二引数以降の引数の渡し方が異なります。applyメソッドは配列でまとめて渡すパターン、callメソッドはカンマ区切りで渡すパターンになります。以下、実行例。
'use strict'; var obj = { foo: 'obj foo value' }; function baz(a, b) { console.log(a + b + this.foo); } baz.apply(obj, ['apply a ', 'apply b ']); baz.call(obj, 'call a ', 'call b ');
結果もそれぞれapply a apply b obj foo value
とcall a call b obj foo value
と出力されます。
コンストラクタの連鎖
継承のようなことができるという認識なのだがあっているのか?という自問自答。FooFunc()クラスのコンストラクタ(a,b)を、BarFunc()とBazFunc()のコンストラクタでコールします。これがapplyメソッドではできない。
'use strict'; function FooFunc(a, b){ this.a = a; this.b = b; } function BarFunc(a, b){ FooFunc.call(this, a, b); this.c = 'bar c'; } function BazFunc(a, b){ FooFunc.call(this, a, b); this.d = 'baz d'; } var bar = new BarFunc('bar a', 'bar b'); var baz = new BazFunc('baz a', 'baz b'); console.log(bar); console.log(baz);
結果は、以下の通り返ってきます。BarFunc()とBazFunc()はそれぞれ独自で設定した値(cとdの値)が設定されています。
BarFunc {a: "bar a", b: "bar b", c: "bar c"} BazFunc {a: "baz a", b: "baz b", d: "baz d"}
無名関数を呼び出す
オブジェクトに関数を定義する際にループのインデックス値を保持させたい場合、無名関数の中で関数オブジェクト(クロージャー)を生成することで、その中で保持させることができます。これもapplyメソッドではできないこと。
'use strict'; var foo = [ {a: 'shimeji'}, {a: 'maitake'}, {a: 'shiitake'} ]; for (var i = 0; i < foo.length; i++){ (function(i) { this.bar = function() { console.log(i + ':' + this.a); }; }).call(foo[i], i); } foo[0].bar(); foo[1].bar(); foo[2].bar();
変数iの値を保持したまま関数を呼び出すことができました。\(^o^)/
0:shimeji 1:maitake 2:shiitake
参考サイト
- MDN: Function.prototype.apply()
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
- MDN: Function.prototype.call()
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/call