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 valuecall 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