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