blog

call apply bind use 比較と手動実装

前置き: このポインティング、スコープ\n\n類似点 - thisの変更\nこれら3つのメソッドを一緒に比較すべき理由は、どれも同じ重要な目的を果たすからです:\n関数が実行される環境、つまりthis...

Dec 13, 2020 · 4 min. read
シェア

前置き: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メソッドの役割や特徴を知ることで、理解を深めるために一度手動で実装することができます。

  1. 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

実行出力と戻り値は、上記のコールメソッド呼び出しと変わりません。

  1. 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);

結果は上記と同じで、他のパラメータが渡されない場合は正常に実行されます。

  1. 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関数は複数の引数を直接渡します。

Read next

アルゴリズムを学ぶ:ソート - 選択ソート

選択ソートとは、配列から最大または最小の要素を選択し、それをキューの先頭または末尾の要素と相互作用させる処理です。 最初に選択処理が行われるので、選択ソートと呼ばれています。 8個の数字がある場合、7ラウンドのソートが必要です。 例えば、最初のラウンドでは、ペアはすべての数字を比較し、その中で最も小さい10を見つけ、その10を配列の先頭に置きます。 2ラウンド目は...

Dec 13, 2020 · 3 min read