blog

コール&アプライド&バインドの使い方、違い、実装

関数プロトタイプチェインのapply、call、bindメソッドは、関数のthisのポイントを変更するために使用される重要な概念です。その使い方や違い、内部実装についてご存知ですか? つまり、最初のパ...

Aug 21, 2020 · 3 min. read
シェア

関数プロトタイプチェインのapplyメソッド、callメソッド、bindメソッドはJavaScriptにおいて重要な概念です。これらのメソッドの使い方、違い、内部実装についてご存知ですか?

呼びかけ

この関数は、プロトタイプチェーンに基づいてFunction.prototype.callメソッドを見つけ、それを実行します:

  • 現在の関数を実行させます;
  • 関数内の thisを、最初に渡された実パラメーターを指すように変更します;
  • 呼び出しに渡された残りの実パラメータを、現在の関数の引数として渡します;

引数がない場合、関数内の thisは、非厳密モードではウィンドウを指し、厳密モードでは未定義を指します。

window.name = 'window';
let obj = {name:' '};
let fn = function(){
 console.log(this.name);
}
fn(); //=> this:window  'window' 厳格なモード=> undefined
//obj.fn(); //  obj.fn is not a function => objにはfn属性はない。

要件:fn実行時に thisがobjを指すようにするには?

obj.fn = fn;
obj.fn();
delete obj.fn; 
//まずobjにfn属性を追加し、次に
fn.call(obj); //this:obj  ' '
fn.call(); //this:window  :undefined
fn.call(null); //this:window  :undefined

つまり、最初のパラメータにnull/undefinedが渡された場合、または渡されなかった場合、thisは非厳密モードのウィンドウを指し、thisは厳密モードで渡されたものであり、そうでない場合、thisはundefinedです。thisは未定義です。

fnにパラメータを渡すとはどういう意味ですか?

function fn(a,b){
 ...
}
 
fn.call(obj); //aとbはどちらもundefined
fn.call(obj,1,2); //this:obj a:1 b:2
fn.call(1,2); //this:1 a:2 b:undefined

では、どのように組み込みの呼び出しメソッドを実装するのでしょうか?

/*
 * @params:
 *	context:最後に変更する関数のthisは 
 * args:関数に渡された実際のパラメータに関する情報
 */
function call(context,...args){
 //contextは、ウィンドウのNULLまたは未定義であり、そうでない場合は、渡されるものである
 context = context == null ? window : context;
 
 //contextオブジェクトでなければならない
 let contextType = typeof context;
 if(!/^(function|object)$/i.test(contextType)){
 	context = Object(context);
 }
 
 let result,
 key = Symbol('key');
 context[key] = this; //関数をオブジェクトのメンバ値として扱う
 result = context[key](...args); //オブジェクトベース[ ]関数を実行する方法は、今回の関数は、このオブジェクトである
 delete context[key]; //使い切ったときにセットメンバーを削除する
 return result; //呼び出しメソッドの実行結果として、関数の戻り値を返す。
}

適用

callメソッドと同じように、関数を実行し、その中の thisキーワードを変更します;

callとapplyの違い:

  • 呼び出しはパラメーターのシングルパスです;
  • は配列に従ってパラメータを渡します;

例を挙げて違いを比べてみてください:

let obj = {name:' '};
let fn = function(a,b){
 console.log(this.name);
}
//objを指すthisと引数10を渡すfnメソッドを実行させる20
// call
fn.call(obj,10,20);
// apply
fn.apply(obj,[10,20]);

バインド

call / applyと同様に、関数内の thisキーワードを変更するためにも使用されます。ただし、bindに基づいて thisを変更することで、現在のメソッドは実行されず、preemptively changeと同様にこれは、事前に thisを変更するのと似ています。

let obj = {name:' '};
let fn = function(a,b){
 console.log(this.name);
}
document.body.onclick=fn; //イベントがトリガーされるとthis->body

ボディをクリックしたときに、fnの thisがobjを指すようにしたい場合は、どうすればいいのでしょうか?

document.body.onclick = fn.call(obj); 

callを使ってポインティングを変更するべきだと思っている人が多いようですが、それは間違いです!この方法で処理するのは、fnをイベントにバインドするのではなく、fnの実行結果をイベントにバインドするのです。

正しい方法:

//イベントに無名関数をバインドする
document.body.onclick = function(){
 //this -> body
 fn.call(obj); 
}
//では、どのようにfnをバインドするのか?
// bind
document.body.onclick = fn.bind(obj);

bindの利点は、あらかじめfnの thisをobjに変更しておき、クリックイベントが発生するまでfnが実行されないことです。メソッドは thisが変更された直後に実行されます。

互換性があるので、バインドメソッドを実装してみましょう!

/*
 * @params:
 *	context:最後に変更する関数のthisは 
 * args:関数に渡された実際のパラメータに関する情報
 */
function bind(context,...params){
 let _this = this;
 return function anonymous(...args){
 //args -> 渡される可能性のあるイベントオブジェクトなどの情報
 _this.call(context,...params.concat(args));
 }
}

callとapplyとbindの違い

call / apply: 関数を即座に実行し、thisを変更します;

バインド:コリエライゼーションの考え方を使い、「処理する関数/変更する this/渡す引数」などをクロージャにあらかじめ格納しておき、後で条件に達したときに、返された無名関数を先に実行し、その無名関数の実行過程で変更 this など => this とパラメータの前処理

Read next