JavaScriptでは、オブジェクト自体またはプロトタイプで必ずしも定義されていない他のオブジェクトの関数またはメソッドを再利用できる場合があります。call()、apply()、bind()メソッドを使用すると、これらのオブジェクトを継承しなくても、他のオブジェクトからメソッドを簡単に借用できます。これは、プロのJavaScript開発者が使用する一般的な方法です。
前提
この記事は、call()、apply()、bind()の使用に関する関連知識と、それらの違いを習得していることを前提としています。
プロトタイプ法
JavaScriptでは、文字列、数値、ブール値などの変更できないプリミティブデータタイプを除いて、ほとんどすべてのデータがオブジェクトです。Arrayは、通常のシーケンスをトラバースおよび変換するのに適したオブジェクトであり、そのプロトタイプには、スライス、結合、プッシュ、ポップなどの便利なメソッドがあります。
一般的な例は、オブジェクトと配列が両方ともリストタイプのデータ構造である場合、オブジェクトは配列からメソッドを「借用」できます。最も一般的に借用されるメソッドはArray.prototype.sliceです。
function myFunc() {
// error, arguments is an array like object, not a real array
arguments.sort();
// "borrow" the Array method slice from its prototype, which takes an array like object (key:value)
// and returns a real array
var args = Array.prototype.slice.call(arguments);
// args is now a real Array, so can use the sort() method from Array
args.sort();
}
myFunc('bananas', 'cherries', 'apples');
callメソッドとapplyメソッドを使用すると、さまざまなコンテキストで関数を呼び出すことができるため、メソッドの借用が可能です。これは、他のオブジェクトを継承せずに既存の関数を再利用するための優れた方法でもあります。実際、配列は、結合やフィルターなど、プロトタイプの多くの一般的なメソッドを定義します。
// takes a string "abc" and produces "a|b|c
Array.prototype.join.call('abc', '|');
// takes a string and removes all non vowels
Array.prototype.filter.call('abcdefghijk', function(val) {
return ['a', 'e', 'i', 'o', 'u'].indexOf(val) !== -1;
}).join('');
オブジェクトは配列メソッドだけでなく文字列も借用できることがわかります。ただし、汎用メソッドはプロトタイプで定義されているため、メソッドを借用するたびにString.prototypeまたはArray.prototypeを使用する必要があります。このように書くことは非常に冗長で、すぐに面倒になります。より効果的な方法は、同じ目的を達成するためにリテラルを使用することです。
文字通りの借用方法を使用する
リテラルはJavaScriptの規則に従う文法構造であり、MDNは次のように説明しています。
JavaScriptでは、リテラルを使用して値を表すことができます。これらは固定値であり、変数ではないか、スクリプトで文字通り指定されます。
リテラルは、プロトタイプメソッドとして省略できます。
[].slice.call(arguments);
[].join.call('abc', '|');
''.toUpperCase.call(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');
これはそれほど冗長ではないように見えますが、メソッドを借用するには[]と ""を直接操作する必要があり、それでも少し醜いです。変数を使用して、リテラルとメソッドへの参照を保存できます。これにより、次のように簡単に記述できます。
var slice = [].slice;
slice.call(arguments);
var join = [].join;
join.call('abc', '|');
var toUpperCase = ''.toUpperCase;
toUpperCase.call(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');
借用したメソッドを参照すると、call()で簡単に呼び出すことができるため、コードを再利用することもできます。冗長性を減らすという原則に基づいて、呼び出すたびにcall()またはapply()を記述せずにメソッドを借用できるかどうかを見てみましょう。
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice(arguments);
var join = Function.prototype.call.bind(Array.prototype.join);
join('abc', '|');
var toUpperCase = Function.prototype.call.bind(String.prototype.toUpperCase);
toUpperCase(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');
ご覧のとおり、Function.prototype.call.bindを使用して、さまざまなプロトタイプから「借用」されたメソッドを静的にバインドできるようになりました。しかし、varスライス= Function.prototype.call.bind(Array.prototype.slice)は実際にどのように機能しますか?
後Function.prototype.call.bind
Function.prototype.call.bindは一見複雑に見えるかもしれませんが、それがどのように機能するかを理解することは非常に役立ちます。
-
Function.prototype.callは一種の参照であり、関数を「呼び出し」、その「this」値を設定して関数で使用できます。
- 「bind」は、「this」値が格納された新しい関数を返すことに注意してください。したがって、.bind(Array.prototype.slice)によって返される新しい関数の「this」は常にArray.prototype.slice関数です。
要約すると、新しい関数は「call」関数を呼び出し、その「this」は「slice」関数です。スライス()を呼び出すと、以前に定義されたメソッドがポイントされます。
オブジェクトをカスタマイズする方法
継承は素晴らしいですが、開発者は通常、オブジェクトまたはモジュール間でいくつかの共通の機能を再利用したいときにそれを使用します。ほとんどの場合、単純な借用方法は複雑になるため、コードの再利用のためだけに継承を使用する必要はありません。
以前は、ネイティブメソッドの借用についてのみ説明しましたが、どのメソッドでも問題ありません。たとえば、次のコードは、ポイントゲームのプレーヤーのスコアを計算できます。
var scoreCalculator = {
getSum: function(results) {
var score = 0;
for (var i = 0, len = results.length; i < len; i++) {
score = score + results[i];
}
return score;
},
getScore: function() {
return scoreCalculator.getSum(this.results) / this.handicap;
}
};
var player1 = {
results: [69, 50, 76],
handicap: 8
};
var player2 = {
results: [23, 4, 58],
handicap: 5
};
var score = Function.prototype.call.bind(scoreCalculator.getScore);
// Score: 24.375
console.log('Score: ' + score(player1));
// Score: 17
console.log('Score: ' + score(player2));
上記の例は非常に鈍いですが、ネイティブメソッドと同様に、ユーザー定義のメソッドを簡単に借用できることがわかります。
総括する
呼び出し、バインド、および適用は、関数の呼び出し方法を変更する可能性があり、関数を借用するときによく使用されます。ほとんどの開発者はネイティブメソッドの借用に精通していますが、カスタムメソッドの借用はめったにありません。
近年、JavaScriptでの機能プログラミングは順調に発展しています。Function.prototype.call.bindメソッドを使用して簡単にするにはどうすればよいですか?そのようなトピックはますます一般的になると推定されています。