Wie funktioniert Array.prototype.slice.call ()?

474

Ich weiß, dass es verwendet wird, um Argumente zu einem echten Array zu machen, aber ich verstehe nicht, was bei der Verwendung passiert Array.prototype.slice.call(arguments)

ilyo
quelle
2
^ etwas anders, da dieser Link nach DOM-Knoten und nicht nach Argumenten fragt. Und ich denke, die Antwort hier ist viel besser, wenn man beschreibt, was "das" intern ist.
Quadratmeter

Antworten:

870

Was unter der Haube passiert, ist, dass, wenn .slice()es normal aufgerufen wird, thises sich um ein Array handelt, das dann nur über dieses Array iteriert und seine Arbeit erledigt.

Wie ist thisin der .slice()Funktion ein Array? Denn wenn Sie dies tun:

object.method();

... das wird objectautomatisch zum Wert von thisin der method(). Also mit:

[1,2,3].slice()

... das [1,2,3]Array wird als Wert von thisin gesetzt .slice().


Aber was wäre, wenn Sie etwas anderes als thisWert ersetzen könnten ? Solange alles, was Sie ersetzen, eine numerische .lengthEigenschaft und eine Reihe von Eigenschaften hat, die numerische Indizes sind, sollte es funktionieren. Dieser Objekttyp wird häufig als Array-ähnliches Objekt bezeichnet .

Mit den Methoden .call()und .apply()können Sie den Wert von in einer Funktion manuell festlegen this. Wenn wir also den Wert von thisin .slice()auf ein Array-ähnliches Objekt setzen , .slice()wird einfach angenommen, dass es mit einem Array funktioniert, und es wird seine Sache tun.

Nehmen Sie dieses einfache Objekt als Beispiel.

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

Dies ist offensichtlich kein Array, aber wenn Sie es als thisWert festlegen können, .slice()funktioniert es einfach, da es wie ein Array aussieht .slice(), um ordnungsgemäß zu funktionieren.

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

Beispiel: http://jsfiddle.net/wSvkv/

Wie Sie in der Konsole sehen können, erwarten wir folgendes Ergebnis:

['three','four'];

Dies passiert also, wenn Sie ein argumentsObjekt als thisWert von festlegen .slice(). Da argumentses eine .lengthEigenschaft und eine Reihe von numerischen Indizes hat, .slice()geht es einfach so, als würde es an einem echten Array arbeiten.

user113716
quelle
7
Gute Antwort! Leider können Sie nicht jedes Objekt auf diese Weise konvertieren, wenn Ihre Objektschlüssel Zeichenfolgenwerte sind, wie in tatsächlichen Worten. Dies schlägt fehl. Behalten Sie also den Inhalt Ihrer Objekte als '0': 'Wert' und nicht als 'Zeichenfolgenname' bei. :'Wert'.
Joopmicroop
6
@Michael: Das Lesen des Quellcodes der Open-Source-JS-Implementierungen ist möglich, es ist jedoch einfacher, nur auf die Sprachspezifikation "ECMAScript" zu verweisen. Hier ist ein Link zur Array.prototype.sliceMethodenbeschreibung.
1
Da Objektschlüssel keine Reihenfolge haben, kann diese spezielle Demonstration in anderen Browsern fehlschlagen. Nicht alle Anbieter sortieren die Schlüssel ihrer Objekte nach Erstellungsreihenfolge.
vsync
1
@vsync: Es ist die for-inAussage, die keine Ordnung garantiert. Der von .slice()definierte Algorithmus definiert eine numerische Reihenfolge, die 0mit dem .lengthdes angegebenen Objekts (oder Arrays oder was auch immer) beginnt und damit endet (nicht inklusive ). Somit ist die Reihenfolge für alle Implementierungen garantiert konsistent.
Keks Monster
7
@vsync: Es ist keine Annahme. Sie können von jedem Objekt eine Bestellung erhalten, wenn Sie diese erzwingen. Nehmen wir an, ich habe var obj = {2:"two", 0:"zero", 1: "one"}. Wenn wir for-indas Objekt aufzählen, gibt es keine Garantie für die Bestellung. Wenn wir jedoch verwenden for, können wir die Reihenfolge manuell erzwingen : for (var i = 0; i < 3; i++) { console.log(obj[i]); }. Jetzt wissen wir, dass die Eigenschaften des Objekts in aufsteigender numerischer Reihenfolge erreicht werden, die wir durch unsere forSchleife definiert haben . Das .slice()macht es. Es ist egal, ob es ein tatsächliches Array hat. Es beginnt nur bei 0und greift in einer aufsteigenden Schleife auf Eigenschaften zu.
Keks Monster
88

Das argumentsObjekt ist eigentlich keine Instanz eines Arrays und verfügt über keine der Array-Methoden. Funktioniert also arguments.slice(...)nicht, da das Argumentobjekt nicht über die Slice-Methode verfügt.

Arrays haben diese Methode, und da das argumentsObjekt einem Array sehr ähnlich ist, sind beide kompatibel. Dies bedeutet, dass wir Array-Methoden mit dem Argument-Objekt verwenden können. Und da Array-Methoden mit Blick auf Arrays erstellt wurden, geben sie eher Arrays als andere Argumentobjekte zurück.

Warum also verwenden Array.prototype? Das Arrayist das Objekt, aus dem wir neue Arrays erstellen ( new Array()), und diese neuen Arrays sind übergebene Methoden und Eigenschaften wie Slice. Diese Methoden werden im [Class].prototypeObjekt gespeichert . Aus Effizienzgründen erhalten wir die Slice-Methode nicht direkt von (new Array()).slice.call()oder [].slice.call(), sondern direkt vom Prototyp. Auf diese Weise müssen wir kein neues Array initialisieren.

Aber warum müssen wir das überhaupt tun? Nun, wie Sie sagten, konvertiert es ein Argumentobjekt in eine Array-Instanz. Der Grund, warum wir Slice verwenden, ist jedoch eher ein "Hack" als alles andere. Die Slice-Methode nimmt ein Slice eines Arrays und gibt dieses Slice als neues Array zurück. Wenn keine Argumente an das Objekt übergeben werden (außer dem Argumentobjekt als Kontext), nimmt die Slice-Methode einen vollständigen Teil des übergebenen "Arrays" (in diesem Fall des Argumentobjekts) und gibt es als neues Array zurück.

Ben Fleming
quelle
Sie können ein Slice aus den hier beschriebenen Gründen verwenden: jspatterns.com/arguments-considered-harmful
KooiInc
44

Normalerweise anrufen

var b = a.slice();

kopiert das Array ain b. Das können wir jedoch nicht

var a = arguments.slice();

weil argumentses kein echtes Array ist und keine sliceMethode hat. Array.prototype.sliceist die sliceFunktion für Arrays und callführt die Funktion mit thisset to aus arguments.

Delan Azabani
quelle
2
Danke, aber warum verwenden prototype? ist keine slicenative ArrayMethode?
Ilyo
2
Beachten Sie, dass dies Arrayeine Konstruktorfunktion ist und die entsprechende "Klasse" ist Array.prototype. Sie können auch verwenden[].slice
user123444555621
4
IlyaD sliceist eine Methode für jede ArrayInstanz, jedoch nicht die ArrayKonstruktorfunktion. Sie verwenden, prototypeum auf Methoden der theoretischen Instanzen eines Konstruktors zuzugreifen.
Delan Azabani
23

Zunächst sollten Sie lesen, wie der Funktionsaufruf in JavaScript funktioniert . Ich vermute, dass allein genug ist, um Ihre Frage zu beantworten. Aber hier ist eine Zusammenfassung dessen, was passiert:

Array.prototype.sliceextrahiert die Methode aus dem Prototyp . Ein direkter Aufruf funktioniert jedoch nicht, da es sich um eine Methode (keine Funktion) handelt und daher einen Kontext (ein aufrufendes Objekt ) erfordert , da es sonst ausgelöst wird .slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

Mit dieser call()Methode können Sie den Kontext einer Methode angeben, sodass diese beiden Aufrufe im Wesentlichen gleichwertig sind:

someObject.slice(1, 2);
slice.call(someObject, 1, 2);

Mit der Ausnahme, dass Ersteres erfordert, dass die sliceMethode in someObjectder Prototypenkette vorhanden ist (wie dies auch der Fall ist Array), während Letzteres die someObjectmanuelle Übergabe des Kontextes ( ) an die Methode ermöglicht.

Letzteres ist auch die Abkürzung für:

var slice = Array.prototype.slice;
slice.call(someObject, 1, 2);

Welches ist das gleiche wie:

Array.prototype.slice.call(someObject, 1, 2);
Marco Roy
quelle
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]
Sam
quelle
16

Array.prototype.slice.call (Argumente) ist die altmodische Methode, um Argumente in ein Array zu konvertieren.

In ECMAScript 2015 können Sie Array.from oder den Spread-Operator verwenden:

let args = Array.from(arguments);

let args = [...arguments];
Alexander Moiseyev
quelle
9

Es liegt daran, wie MDN feststellt

Das Argumentobjekt ist kein Array. Es ähnelt einem Array, hat jedoch keine Array-Eigenschaften außer der Länge. Zum Beispiel hat es nicht die Pop-Methode. Es kann jedoch in ein reales Array konvertiert werden:

Hier rufen wir slicedas native Objekt auf Arrayund nicht dessen Implementierung und deshalb das Extra.prototype

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

Vergessen Sie nicht, dass eine grundlegende Grundlage dieses Verhaltens das Typ-Casting ist, das vollständig in die JS-Engine integriert ist.

Slice nimmt nur ein Objekt (dank der vorhandenen Eigenschaft arguments.length) und gibt ein Array-Objekt zurück, das nach Ausführung aller Operationen umgewandelt wurde.

Dieselbe Logik, die Sie testen können, wenn Sie versuchen, die String-Methode mit einem INT-Wert zu behandeln:

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

Und das erklärt die obige Aussage.

Andrey
quelle
1

Es verwendet die sliceMethode, die Arrays haben, und ruft sie mit thisdem argumentsObjekt auf. Dies bedeutet, dass es so heißt, als hätten Sie arguments.slice()angenommenarguments eine solche Methode gibt.

Wenn Sie ein Slice ohne Argumente erstellen, werden einfach alle Elemente übernommen, sodass die Elemente einfach argumentsin ein Array kopiert werden .

DiebMaster
quelle
1

Nehmen wir an, Sie haben: function.apply(thisArg, argArray )

Die Methode apply ruft eine Funktion auf und übergibt das daran gebundene Objekt und ein optionales Array von Argumenten.

Die Slice () -Methode wählt einen Teil eines Arrays aus und gibt das neue Array zurück.

Wenn Sie also Array.prototype.slice.apply(arguments, [0])das Array aufrufen, wird die Slice-Methode für Argumente aufgerufen (bind).

user278064
quelle
1

Vielleicht etwas spät, aber die Antwort auf all dieses Durcheinander ist, dass call () in JS für die Vererbung verwendet wird. Wenn wir dies zum Beispiel mit Python oder PHP vergleichen, wird call jeweils als super () verwendet. init () oder parent :: _ construct ().

Dies ist ein Beispiel für seine Verwendung, das alles verdeutlicht:

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

Referenz: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance

Davide Pugliese
quelle
0

Wenn .slice () normal aufgerufen wird, handelt es sich um ein Array. Anschließend wird das Array einfach durchlaufen und seine Arbeit erledigt.

 //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"]
Pablo
quelle
-1

Ich schreibe das nur, um mich daran zu erinnern ...

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

Oder verwenden Sie einfach diese praktische Funktion $ A , um die meisten Dinge in ein Array zu verwandeln.

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);
}

Beispiel Verwendung ...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
Orwellophil
quelle