引数を実数配列にするために使われていることはわかっていますが、Array.prototype.slice.call(arguments)
を使ったときに何が起こるかわかりません。
内部で起こることは、.slice()
が普通に呼ばれるとき、this
は配列であり、そしてそれは単にその配列を反復処理し、そしてその仕事をするということです。
.slice()
関数のthis
はどのように配列になりますか?あなたがするとき:
object.method();
... object
は自動的にmethod()
のthis
の値になります。だからと:
[1,2,3].slice()
... [1,2,3]
配列は.slice()
のthis
の値として設定されます。
しかし、もしあなたがthis
の値として他の何かを代用することができたらどうでしょうか?あなたが代用するものがすべて、数値の.length
プロパティ、および数値のインデックスであるプロパティの束を持っている限り、それは動作するはずです。この種のオブジェクトは、しばしばarray-like objectと呼ばれます。
.call()
メソッドと.apply()
メソッドを使うと、manualに関数のthis
の値を設定できます。 .slice()
のthis
の値を配列のようなオブジェクトに設定すると、.slice()
はassume配列で作業して、そしてそれをするでしょう。
例としてこの単純なオブジェクトを取ります。
var my_object = {
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three',
'4': 'four',
length: 5
};
これは明らかに配列ではありませんが、.slice()
のthis
値として設定できれば、.slice()
が正しく機能するための十分な配列のように見えるので、うまく機能するでしょう。
var sliced = Array.prototype.slice.call( my_object, 3 );
あなたがコンソールで見ることができるように、結果は我々が期待するものです:
['three','four'];
そのため、arguments
オブジェクトを.slice()
のthis
値として設定すると、これが起こります。 arguments
は.length
プロパティと多数の数値インデックスを持っているので、.slice()
はあたかもそれが実際の配列で動作しているかのようにその動作を進めます。
arguments
オブジェクトは実際にはArrayのインスタンスではなく、Arrayメソッドも持ちません。そのため、argumentsオブジェクトにsliceメソッドがないため、arguments.slice(...)
は機能しません。
配列はこのメソッドを持っています、そしてarguments
オブジェクトは配列に非常に似ているので、2つは互換性があります。これは、argumentsオブジェクトと共に配列メソッドを使用できることを意味します。そして、配列メソッドは配列を念頭に置いて構築されているので、他の引数オブジェクトではなく配列を返します。
では、なぜArray.prototype
を使うのですか? Array
は、(new Array()
)から新しい配列を作成するためのオブジェクトです。これらの新しい配列には、sliceのようにメソッドとプロパティが渡されます。これらのメソッドは[Class].prototype
オブジェクトに格納されています。そのため、効率のために、(new Array()).slice.call()
や[].slice.call()
でsliceメソッドにアクセスするのではなく、プロトタイプから直接取得します。これは、新しい配列を初期化する必要がないためです。
しかし、なぜそんなことを最初からやらなければならないのでしょうか。まあ、あなたが言ったように、それは引数オブジェクトをArrayインスタンスに変換します。ただし、スライスを使用する理由は、何よりも「ハック」のようなものです。 sliceメソッドは、あなたが推測した通り、配列のスライスを取り、そのスライスを新しい配列として返します。引数を渡さないと(コンテキストとしてargumentsオブジェクトを除く)、sliceメソッドは渡された "array"(この場合はargumentsオブジェクト)の完全なチャンクを取得して新しい配列として返します。
通常、電話
var b = a.slice();
配列a
をb
にコピーします。しかし、できません
var a = arguments.slice();
arguments
は実際の配列ではなく、メソッドとしてslice
を持たないからです。 Array.prototype.slice
は配列のslice
関数で、call
はthis
をarguments
に設定して関数を実行します。
// We can apply `slice` from `Array.prototype`:
Array.prototype.slice.call([]); //-> []
// Since `slice` is available on an array's prototype chain,
'slice' in []; //-> true
[].slice === Array.prototype.slice; //-> true
// … we can just invoke it directly:
[].slice(); //-> []
// `arguments` has no `slice` method
'slice' in arguments; //-> false
// … but we can apply it the same way:
Array.prototype.slice.call(arguments); //-> […]
// In fact, though `slice` belongs to `Array.prototype`,
// it can operate on any array-like object:
Array.prototype.slice.call({0: 1, length: 1}); //-> [1]
最初に、 JavaScriptで関数呼び出しがどのように機能するか と読む必要があります。あなたの質問に答えるだけで十分だと思います。しかし、ここで何が起こっているかの要約です:
Array.prototype.slice
は、slice
の プロトタイプ から Array
method を抽出します。しかし、それを直接呼び出すことは機能しません メソッドではないため(関数ではない) であり、したがってコンテキスト(呼び出しオブジェクト、this
)が必要です。そうでなければ、Uncaught TypeError: Array.prototype.slice called on null or undefined
がスローされます。
call()
メソッドを使用すると、メソッドのコンテキストを指定して、基本的にこれら2つの呼び出しを同等にすることができます。
someObject.slice(1, 2);
slice.call(someObject, 1, 2);
前者はslice
のプロトタイプチェーンにsomeObject
メソッドが存在する必要があることを除いて(Array
の場合と同様)、後者ではコンテキスト(someObject
)をメソッドに手動で渡すことができます。
また、後者は以下の略です:
var slice = Array.prototype.slice;
slice.call(someObject, 1, 2);
これは次と同じです:
Array.prototype.slice.call(someObject, 1, 2);
Array.prototype.slice.call(arguments)は、引数を配列に変換するための昔ながらの方法です。
ECMAScript 2015では、Array.fromまたはspread演算子を使用できます。
let args = Array.from(arguments);
let args = [...arguments];
その理由は、 MDNがメモしているように
Argumentsオブジェクトは配列ではありません。これは配列に似ていますが、長さ以外の配列プロパティはありません。たとえば、popメソッドはありません。ただし、実際の配列に変換することができます。
ここではネイティブオブジェクトslice
でArray
を呼び出していて、その実装ではないので、追加の.prototype
が必要になります。
var args = Array.prototype.slice.call(arguments);
忘れないでください。この動作の低レベルの基本は、JSエンジンに完全に統合された型キャストです。
Sliceは単に(既存のarguments.lengthプロパティのおかげで)オブジェクトを受け取り、それに対してすべての操作を行った後にキャストされたarray-objectを返します。
String-methodをINT値で処理しようとした場合にテストできるものと同じ論理です。
String.prototype.bold.call(11); // returns "<b>11</b>"
そしてそれは上の文を説明します。
これはslice
メソッドの配列hasを使用し、this
をarguments
オブジェクトとして呼び出します。これは、arguments
がそのようなメソッドを持っていると仮定して、arguments.slice()
を行ったかのようにそれを呼び出すことを意味します。
引数を指定せずにスライスを作成すると、すべての要素が単純に取得されます。したがって、要素はarguments
から配列に単純にコピーされます。
あなたが持っていると仮定しましょう:function.apply(thisArg, argArray )
Applyメソッドは関数を呼び出し、これにバインドされるオブジェクトと引数のオプションの配列を渡します。
Slice()メソッドは、配列の一部を選択して新しい配列を返します。
そのため、Array.prototype.slice.apply(arguments, [0])
を呼び出すと、引数に対して配列スライスメソッドが呼び出されます(bind)。
少し遅れるかもしれませんが、この問題のすべてに対する答えは、call()が継承のためにJSで使用されていることです。たとえば、これをPythonまたはPHPと比較すると、callはそれぞれsuper()。init()またはparent :: _ construct()として使用されます。
これは、すべてを明確にするその使用法の例です。
function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);
this.subject = subject;
}
参照: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance
.slice()が正常に呼び出された場合、これは配列であり、それからその配列を反復処理してその作業を行います。
//ARGUMENTS
function func(){
console.log(arguments);//[1, 2, 3, 4]
//var arrArguments = arguments.slice();//Uncaught TypeError: undefined is not a function
var arrArguments = [].slice.call(arguments);//cp array with explicity THIS
arrArguments.Push('new');
console.log(arrArguments)
}
func(1,2,3,4)//[1, 2, 3, 4, "new"]