前置き:thisポインティング、スコーピング
類似点 - this の変更
なぜこの3つの方法をまとめて比較する必要があるかというと、どれも同じように重要な目的を果たすからです:
- 関数の実行コンテキストを変更します。
コードによる簡単なデモンストレーション:
function say() {
console.info("name", this.name);
}
var personA = {
name: "A",
say,
};
var personB = { name: "B" };
say();
personA.say();
personA.say.call(personB);
personA.say.apply(personB);
personA.say.bind(personB)();
//name undefined
//name A
//name B
//name B
//name B
上記のコードでは、sayメソッドが直接呼び出されます。thisはグローバルオブジェクトです。もしBを出力したいが、personBにsayメソッドがない場合、どうすればいいでしょうか?
callとapplyは、関数内のthisを、対応する関数の最初のパラメータであるpersonBの名前を指すように変更します。お気づきかもしれませんが、bind関数がpersonBに渡された後にもう一つ呼び出しがあります。bindメソッドはthisを変更しても関数を実行せず、新しい関数を返すからです。
call/apply bind とは異なり
上記のように、bindの返り値はcall/applyの返り値とは異なります。**bind呼び出しは、bind関数に渡された最初のパラメータを指すようにthisが変更された新しい関数を返します。この関数を実行する必要がある場合は、新しい関数をもう一度呼び出す必要があります。callメソッドとapplyメソッドが呼び出され、呼び出しの後に関数が直接実行されます。**これが call/apply メソッドと bind メソッドの最大の違いです。
callメソッドとapplyメソッドの共通点と相違点ですが、callメソッドもapplyメソッドも、最初のパラメータはcall/applyを呼び出した後の関数のthisを指します。callメソッドは複数のパラメータのリストを受け取りますが、applyメソッドは個々のパラメータを要素とする配列を受け取ります。
コールメソッド::
function.call(thisArg, arg1, arg2, ...)
this""Argは必須ではありません。値を渡さない場合、または値が未定義/nullの場合、関数のthis はグローバルオブジェクトを指します。
適用方法:
function.apply(thisArg, [argsArray])
適用する最初の引数は必須で、2番目の引数の要素は別の引数として関数に渡されます。
callとapplyの戻り値は、thisを変更した後の関数の戻り値です。
以下はコールとアプリの例です:
function say(age, gender) {
console.info("name/age/gender", this.name + "/" + age + "/" + gender);
}
var personA = {
name: "A",
say,
};
var personB = { name: "B" };
personA.say.call(personB, 20, "man");
personA.say.apply(personB, [24, "woman"]);
bind/call/apply メソッドを手動で実装する場合
call/aplly/bindメソッドの役割や特徴を知ることで、理解を深めるために一度手動で実装することができます。
- callメソッドの通常の使用例を見てみましょう:
const a = { name: "xw" };
function say(gender, age) {
const info = "name/gender/age:" + this.name + "/" + gender + "/" + age;
console.info(info);
return " :" + info;
}
const data = say.call(a, "man", 20);
console.info(data);
//name/gender/age:xw/man/20
//data :name/gender/age:xw/man/20
sayはcallで呼び出され、say自身は2つの引数を取り、出力に使われ、戻り値を持ちます。このように実装できます:
Function.prototype.MyCall = function (context) {
var env = context || window; //渡さない場合は、グローバルな
env.fun = this; // thisこれはMycallを呼び出し、envにフックする関数だ。ランタイムはsay関数だ。
var args = [...arguments].slice(1); //番目の開始引数を取得する
var result = env.fun(...args); // 関数の実行と引数の受け渡しは、say(...args)
delete env.fn;
return result;
};
実行してから電話してください:
const data = say.MyCall(a, "man", 20); //name/gender/age:xw/man/20
console.info("data", data); // name/gender/age:xw/man/20
実行出力と戻り値は、上記のコールメソッド呼び出しと変わりません。
- applyの実装は、引数が配列形式であることを除けば、callと非常によく似ています。
Function.prototype.MyApply = function (context) {
var env = context || window;
env.fun = this;
var result;
if (arguments[1]) { //第2引数は、判断のレイヤーを追加するために拡張する必要がある。
result = env.fun(...arguments[1]);
} else {
result = env.fun();
}
delete env.fn;
return result;
};
//
const data = say.MyApply(a, ["man", 20]);
console.info("data", data);
結果は上記と同じで、他のパラメータが渡されない場合は正常に実行されます。
- bindメソッドです。関数を直接実行する apply や call とは異なり、 bind は関数への参照を返します。
コリエリゼーション:複数の引数を取る関数を、単一の引数を取り、残りの引数を取って結果を返す新しい関数に変換します。
つまり、ある関数がfunctionA(a,b)で呼び出された場合、コリアライズ後の関数functionBは次のようにfunctionB(a)と呼ばれるはずです。
Function.prototype.MyBind = function (context) {
var inThis = this;
var args = [...arguments].slice(1);
return function fun() {
if (this instanceof fun) { //コンストラクタとそうでないものの区別
return new inThis(...args, ...arguments); // 引数を2回取得するコリエリゼーションをサポートする
} else {
return inThis.apply(context, args.concat(...arguments));
}
};
};
その後、通話テストが行われましたが、これも予想通りでした:
const a = { name: "xw" };
function say() {
console.info("name is", this.name);
}
say.MyBind(a)();
// name is xw
まとめ
call、apply、bindはすべて、関数のthisを、call/apply/bind関数の最初のパラメータを指すように変更するために使用されます。
applyは他の引数の配列を受け取りますが、call関数は複数の引数を直接渡します。