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


474

引数を実際の配列にするために使用されていることはわかっていますが、使用するとどうなるかわかりません Array.prototype.slice.call(arguments)


2
^リンクでは、引数ではなくDOMノードについて質問するため、少し異なります。そして、私はここでの答えは、「これ」が何であるかを内部的に説明することではるかに良くなると思います。
sqram

回答:


870

内部で何が起こるか.slice()というと、通常はthisがArrayであり、それがArrayを反復処理し、その処理を実行するだけです。

どのようthis.slice()配列が機能?あなたがするとき:

object.method();

... object自動的にの値thisになりmethod()ます。だから:

[1,2,3].slice()

... [1,2,3]配列はthisin の値として設定されます.slice()


しかし、this値を他のものに置き換えることができるとしたらどうでしょうか。置換するものが数値.lengthプロパティを持ち、数値インデックスである一連のプロパティを持っている限り、それは機能するはずです。このタイプのオブジェクトは、配列のようなオブジェクトと呼ばれることがよくあります

.call()および.apply()方法は、あなたが聞かせて手動での値を設定するthis機能で。我々はの値を設定するのであればthis.slice()配列のようなオブジェクト.slice()ちょうどますと仮定し、それはArrayで働いているし、そのことを行います。

例として、この単純なオブジェクトを取り上げます。

var my_object = {
    '0': 'zero',
    '1': 'one',
    '2': 'two',
    '3': 'three',
    '4': 'four',
    length: 5
};

これは明らかに配列ではありませんが、のthis値として設定できる場合は.slice().slice()正しく機能するための配列のように見えるため、機能します。

var sliced = Array.prototype.slice.call( my_object, 3 );

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

コンソールで確認できるように、結果は期待どおりです。

['three','four'];

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


7
正解です。しかし残念なことに、実際の単語のようにオブジェクトキーが文字列値である場合は、この方法でオブジェクトだけを変換することはできません。これは失敗するため、オブジェクトのコンテンツを 'stringName'ではなく '0': 'value'のままにします。 :'値'。
joopmicroop 2013

6
@Michael:オープンソースのJS実装のソースコードを読み取ることは可能ですが、「ECMAScript」言語仕様を参照するだけの方が簡単です。メソッドの説明へのリンクは次のArray.prototype.sliceとおりです。

1
オブジェクトキーには順序がないため、この特定のデモは他のブラウザでは失敗する可能性があります。すべてのベンダーがオブジェクトのキーを作成順にソートするわけではありません。
vsync

1
@vsync:for-in順序を保証しないステートメントです。で使用されるアルゴリズムは、指定されたオブジェクト(または配列など)ので.slice()始まり、含まれ0ない(非包括的)数値順序を定義します.length。したがって、順序はすべての実装にわたって一貫していることが保証されています。
クッキーモンスター

7
@vsync:これは前提ではありません。強制すれば、どのオブジェクトからでも順序を取得できます。私が持っているとしましょうvar obj = {2:"two", 0:"zero", 1: "one"}for-inオブジェクトの列挙に使用する場合、順序は保証されません。ただし、を使用する場合for、手動で順序を強制できますfor (var i = 0; i < 3; i++) { console.log(obj[i]); }。これで、forループで定義した数値の昇順でオブジェクトのプロパティに到達することがわかりました。それが何をするか.slice()です。実際の配列があるかどうかは関係ありません。それは単に開始し0、昇順ループでプロパティにアクセスします。
クッキーモンスター

88

argumentsオブジェクトは、実際には配列のインスタンスではなく、配列のいずれかの方法を持っていません。したがって、arguments.slice(...)argumentsオブジェクトにはsliceメソッドがないため、機能しません。

配列にはこのメソッドがあり、argumentsオブジェクトは配列に非常に似ているため、2つは互換性があります。これは、argumentsオブジェクトで配列メソッドを使用できることを意味します。また、配列メソッドは配列を考慮して作成されているため、他の引数オブ​​ジェクトではなく配列を返します。

なぜ使用するのArray.prototypeですか?Array我々は(から新しいアレイを作成するオブジェクトであるnew Array())、およびこれらの新しいアレイは、スライスのように、メソッドとプロパティを渡されます。これらのメソッドは[Class].prototypeオブジェクトに格納されます。したがって、効率を上げるために、(new Array()).slice.call()または[].slice.call()でスライスメソッドにアクセスする代わりに、プロトタイプから直接取得します。これは、新しい配列を初期化する必要がないようにするためです。

しかし、なぜこれを最初に行う必要があるのでしょうか。さて、あなたが言ったように、それは引数オブジェクトを配列インスタンスに変換します。ただし、sliceを使用する理由は、何よりも「ハック」に近いものです。あなたが推測したとおり、sliceメソッドは配列のスライスを取り、そのスライスを新しい配列として返します。引数を渡さない場合(引数オブジェクトをコンテキストとして)、sliceメソッドは渡された "配列"(この場合は、引数オブジェクト)の完全なチャンクを取得して、新しい配列として返します。


ここで説明する理由により、スライスを使用したい場合があります:jspatterns.com/arguments-considered-harmful
KooiInc

44

通常、呼び出す

var b = a.slice();

配列をにコピーabます。しかし、私たちはできません

var a = arguments.slice();

これargumentsは、実際の配列ではなくslice、メソッドとしてもないためです。Array.prototype.sliceslice配列callの関数で、にthis設定して関数を実行しますarguments


2
ありがとうございますが、なぜ使用するのprototypeですか?sliceネイティブArrayメソッドではないですか?
ilyo 2011

2
Arrayはコンストラクタ関数であり、対応する「クラス」はであることに注意してくださいArray.prototype。次も使用できます[].slice
user123444555621

4
IlyaD sliceは各Arrayインスタンスのメソッドですが、Arrayコンストラクター関数ではありません。prototypeコンストラクターの理論上のインスタンスのメソッドにアクセスするために使用します。
Delan Azabani、2011

23

まず、JavaScriptで関数呼び出しどのように機能するかをお読みください。私はあなたの質問に答えるには一人で十分だと思います。しかし、これが起こっていることの要約です:

Array.prototype.sliceプロトタイプからメソッドを抽出します。ただし、これはメソッド(関数ではない)であり、コンテキスト(呼び出し元のオブジェクト)が必要であるため、直接呼び出しても機能しません。それ以外の場合はをスローします。slice ArraythisUncaught 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);

22
// 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]

16

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

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

let args = Array.from(arguments);

let args = [...arguments];

9

その理由は、MDNが注記しているように

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

ここでは、その実装ではなくsliceネイティブオブジェクトArrayを呼び出しています。そのため、追加の.prototype

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

4

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

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

文字列メソッドをINT値で処理しようとする場合にテストできるのと同じロジック:

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

そして、それは上記の説明を説明しています。


1

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

引数なしでスライスを作成すると、単純にすべての要素が取得されますarguments。つまり、要素がから配列にコピーされます。


1

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

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

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

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


1

少し遅いかもしれませんが、この混乱のすべてに対する答えは、JSで継承のためにcall()が使用されることです。これを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


0

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

-1

私はこれを思い出させるためにこれを書いています...

    Array.prototype.slice.call(arguments);
==  Array.prototype.slice(arguments[1], arguments[2], arguments[3], ...)
==  [ arguments[1], arguments[2], arguments[3], ... ]

または、この便利な関数$ Aを使用して、ほとんどのものを配列に変換します。

function hasArrayNature(a) {
    return !!a && (typeof a == "object" || typeof a == "function") && "length" in a && !("setInterval" in a) && (Object.prototype.toString.call(a) === "[object Array]" || "callee" in a || "item" in a);
}

function $A(b) {
    if (!hasArrayNature(b)) return [ b ];
    if (b.item) {
        var a = b.length, c = new Array(a);
        while (a--) c[a] = b[a];
        return c;
    }
    return Array.prototype.slice.call(b);
}

使用例...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.