web-dev-qa-db-ja.com

Array.prototype.slice.call()はどのように動作しますか?

引数を実数配列にするために使われていることはわかっていますが、Array.prototype.slice.call(arguments)を使ったときに何が起こるかわかりません。

441
ilyo

内部で起こることは、.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 );

例:http://jsfiddle.net/wSvkv/

あなたがコンソールで見ることができるように、結果は我々が期待するものです:

['three','four'];

そのため、argumentsオブジェクトを.slice()this値として設定すると、これが起こります。 arguments.lengthプロパティと多数の数値インデックスを持っているので、.slice()はあたかもそれが実際の配列で動作しているかのようにその動作を進めます。

828
user113716

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オブジェクト)の完全なチャンクを取得して新しい配列として返します。

80
Ben Fleming

通常、電話

var b = a.slice();

配列abにコピーします。しかし、できません

var a = arguments.slice();

argumentsは実際の配列ではなく、メソッドとしてsliceを持たないからです。 Array.prototype.sliceは配列のslice関数で、callthisargumentsに設定して関数を実行します。

41
Delan Azabani
// 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]
21
sam

最初に、 JavaScriptで関数呼び出しがどのように機能するか と読む必要があります。あなたの質問に答えるだけで十分だと思います。しかし、ここで何が起こっているかの要約です:

Array.prototype.sliceは、sliceプロトタイプ から Arraymethod を抽出します。しかし、それを直接呼び出すことは機能しません メソッドではないため(関数ではない) であり、したがってコンテキスト(呼び出しオブジェクト、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);
18
Marco Roy

Array.prototype.slice.call(arguments)は、引数を配列に変換するための昔ながらの方法です。

ECMAScript 2015では、Array.fromまたはspread演算子を使用できます。

let args = Array.from(arguments);

let args = [...arguments];
10

その理由は、 MDNがメモしているように

Argumentsオブジェクトは配列ではありません。これは配列に似ていますが、長さ以外の配列プロパティはありません。たとえば、popメソッドはありません。ただし、実際の配列に変換することができます。

ここではネイティブオブジェクトsliceArrayを呼び出していて、その実装ではないので、追加の.prototypeが必要になります。

var args = Array.prototype.slice.call(arguments);
9
naveen

忘れないでください。この動作の低レベルの基本は、JSエンジンに完全に統合された型キャストです。

Sliceは単に(既存のarguments.lengthプロパティのおかげで)オブジェクトを受け取り、それに対してすべての操作を行った後にキャストされたarray-objectを返します。

String-methodをINT値で処理しようとした場合にテストできるものと同じ論理です。

String.prototype.bold.call(11);  // returns "<b>11</b>"

そしてそれは上の文を説明します。

4
Andrey

これはsliceメソッドの配列hasを使用し、thisargumentsオブジェクトとして呼び出します。これは、argumentsがそのようなメソッドを持っていると仮定して、arguments.slice()を行ったかのようにそれを呼び出すことを意味します。

引数を指定せずにスライスを作成すると、すべての要素が単純に取得されます。したがって、要素はargumentsから配列に単純にコピーされます。

1
ThiefMaster

あなたが持っていると仮定しましょう:function.apply(thisArg, argArray )

Applyメソッドは関数を呼び出し、これにバインドされるオブジェクトと引数のオプションの配列を渡します。

Slice()メソッドは、配列の一部を選択して新しい配列を返します。

そのため、Array.prototype.slice.apply(arguments, [0])を呼び出すと、引数に対して配列スライスメソッドが呼び出されます(bind)。

1
user278064

少し遅れるかもしれませんが、この問題のすべてに対する答えは、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

1
Davide Pugliese

.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"]
0
Pablo