Ich habe den folgenden Code in der Mailingliste von es-execute gefunden:
Array.apply(null, { length: 5 }).map(Number.call, Number);
Dies erzeugt
[0, 1, 2, 3, 4]
Warum ist das das Ergebnis des Codes? Was passiert hier?
javascript
ecmascript-5
Benjamin Gruenbaum
quelle
quelle
Array.apply(null, Array(30)).map(Number.call, Number)
ist einfacher zu lesen, da nicht vorgetäuscht wird, ein einfaches Objekt sei ein Array.Antworten:
Um diesen "Hack" zu verstehen, müssen Sie verschiedene Dinge verstehen:
Array(5).map(...)
Function.prototype.apply
mit Argumenten um?Array
mit mehreren Argumenten um?Number
Funktion mit Argumenten umgehtFunction.prototype.call
machtEs handelt sich um ziemlich fortgeschrittene Themen in Javascript, daher wird dies mehr als lang sein. Wir werden von oben beginnen. Anschnallen!
1. Warum nicht einfach
Array(5).map
?Was ist eigentlich ein Array? Ein reguläres Objekt, das Ganzzahlschlüssel enthält, die Werten zugeordnet sind. Es hat andere Besonderheiten, zum Beispiel die magische
length
Variable, aber im Kern ist es eine regulärekey => value
Karte, genau wie jedes andere Objekt. Lass uns ein bisschen mit Arrays spielen, sollen wir?Wir kommen zu dem inhärenten Unterschied zwischen der Anzahl der Elemente im Array
arr.length
und der Anzahl derkey=>value
Zuordnungen im Array, die sich von unterscheiden könnenarr.length
.Die Erweiterung des Arrays über
arr.length
keine keine neuen erstellenkey=>value
Zuordnungen, so ist es nicht , dass das Array undefinierte Werte hat, es verfügt nicht über diese Tasten . Und was passiert, wenn Sie versuchen, auf eine nicht vorhandene Eigenschaft zuzugreifen? Du verstehstundefined
.Jetzt können wir unsere Köpfe ein wenig heben und sehen, warum Funktionen wie
arr.map
diese Eigenschaften nicht überschreiten. Wennarr[3]
nur undefiniert wäre und der Schlüssel vorhanden wäre, würden alle diese Array-Funktionen wie jeder andere Wert darüber hinweggehen:Ich habe absichtlich einen Methodenaufruf verwendet, um weiter zu beweisen, dass der Schlüssel selbst nie vorhanden war: Das Aufrufen
undefined.toUpperCase
hätte einen Fehler ausgelöst, aber dies war nicht der Fall. Um zu beweisen , dass :Und jetzt kommen wir zu meinem Punkt: Wie
Array(N)
geht es den Dingen? Abschnitt 15.4.2.2 beschreibt den Prozess. Es gibt eine Menge Hokuspokus, die uns egal sind, aber wenn Sie es schaffen, zwischen den Zeilen zu lesen (oder Sie können mir in dieser Zeile einfach vertrauen, aber nicht), läuft es im Grunde darauf hinaus:(arbeitet unter der Annahme (die in der tatsächlichen Spezifikation überprüft wird), dass
len
es sich um eine gültige uint32 handelt und nicht um eine beliebige Anzahl von Werten)Jetzt können Sie sehen, warum dies
Array(5).map(...)
nicht funktioniert - wir definieren keinelen
Elemente im Array, wir erstellen keinekey => value
Zuordnungen, wir ändern einfach dielength
Eigenschaft.Nachdem wir das aus dem Weg geräumt haben, schauen wir uns die zweite magische Sache an:
2. Wie
Function.prototype.apply
funktioniertIm
apply
Grunde genommen wird ein Array genommen und als Argument eines Funktionsaufrufs abgewickelt. Das bedeutet, dass Folgendes ziemlich gleich ist:Jetzt können wir den Prozess der Funktionsweise
apply
vereinfachen, indem wir einfach diearguments
spezielle Variable protokollieren :Es ist einfach, meine Behauptung im vorletzten Beispiel zu beweisen:
(Ja, Wortspiel beabsichtigt). Das
key => value
Mapping war möglicherweise nicht in dem Array vorhanden, an das wir übergeben habenapply
, aber es ist sicherlich in derarguments
Variablen vorhanden. Es ist der gleiche Grund, warum das letzte Beispiel funktioniert: Die Schlüssel sind für das Objekt, das wir übergeben, nicht vorhanden, aber sie sind in vorhandenarguments
.Warum ist das so? Schauen wir uns Abschnitt 15.3.4.3 an , in dem
Function.prototype.apply
definiert ist. Meistens Dinge, die uns egal sind, aber hier ist der interessante Teil:Was im Grunde bedeutet :
argArray.length
. Die Spezifikation führt dann eine einfachefor
Schleife überlength
Elemente durch und erstellt einenlist
der entsprechenden Werte (list
ist ein interner Voodoo, aber im Grunde ist es ein Array). In Bezug auf sehr, sehr losen Code:argArray
In diesem Fall müssen wir also nur ein Objekt mit einerlength
Eigenschaft nachahmen . Und jetzt können wir sehen, warum die Werte undefiniert sind, die Schlüssel jedoch nicht.arguments
Wir erstellen diekey=>value
Zuordnungen.Puh, das war vielleicht nicht kürzer als der vorherige Teil. Aber wenn wir fertig sind, wird es Kuchen geben, also sei geduldig! Nach dem folgenden Abschnitt (der, wie ich verspreche, kurz sein wird) können wir jedoch beginnen, den Ausdruck zu zerlegen. Falls Sie es vergessen haben, war die Frage, wie Folgendes funktioniert:
3. Wie geht man
Array
mit mehreren Argumenten um?So! Wir haben gesehen, was passiert, wenn Sie ein
length
Argument an übergebenArray
, aber im Ausdruck übergeben wir mehrere Dinge als Argumente (ein Array von 5undefined
, um genau zu sein). In Abschnitt 15.4.2.1 erfahren Sie , was zu tun ist. Der letzte Absatz ist alles, was uns wichtig ist, und er ist wirklich seltsam formuliert , aber es läuft irgendwie auf Folgendes hinaus:Tada! Wir erhalten ein Array mit mehreren undefinierten Werten und geben ein Array dieser undefinierten Werte zurück.
Der erste Teil des Ausdrucks
Schließlich können wir Folgendes entschlüsseln:
Wir haben gesehen, dass es ein Array zurückgibt, das 5 undefinierte Werte enthält, wobei alle Schlüssel vorhanden sind.
Nun zum zweiten Teil des Ausdrucks:
Dies wird der einfachere, nicht verschlungene Teil sein, da er nicht so sehr auf obskuren Hacks beruht.
4. Wie
Number
behandelt man Eingaben?Doing
Number(something)
( Abschnitt 15.7.1 ) konvertiertsomething
in eine Zahl, und das ist alles. Wie das geht, ist etwas kompliziert, besonders bei Strings, aber die Operation wird in Abschnitt 9.3 definiert, falls Sie interessiert sind.5. Spiele von
Function.prototype.call
call
ist seinapply
Bruder, definiert in Abschnitt 15.3.4.4 . Anstatt ein Array von Argumenten zu verwenden, werden nur die empfangenen Argumente verwendet und weitergeleitet.Die Dinge werden interessant, wenn Sie mehr als eine
call
aneinander ketten und das Unheimliche auf 11 drehen:Dies ist ziemlich wertvoll, bis Sie verstehen, was los ist.
log.call
ist nur eine Funktion, die dercall
Methode einer anderen Funktion entspricht , und hat als solche auch einecall
Methode für sich:Und was macht
call
das? Es akzeptiert einethisArg
Reihe von Argumenten und ruft seine übergeordnete Funktion auf. Wir können es definieren überapply
(wieder sehr lockerer Code, funktioniert nicht):Lassen Sie uns verfolgen, wie dies abläuft:
Der spätere Teil oder das
.map
von allemEs ist noch nicht vorbei. Mal sehen, was passiert, wenn Sie den meisten Array-Methoden eine Funktion bereitstellen:
Wenn wir selbst kein
this
Argument angeben, wird standardmäßig verwendetwindow
. Beachten Sie die Reihenfolge, in der die Argumente für unseren Rückruf bereitgestellt werden, und lassen Sie es uns noch einmal bis 11 verrückt machen:Whoa whoa whoa ... lass uns ein bisschen zurücktreten. Was ist denn hier los? Wir können in Abschnitt 15.4.4.18 sehen , wo
forEach
Folgendes definiert ist:Also, wir bekommen das:
Jetzt können wir sehen, wie es
.map(Number.call, Number)
funktioniert:Dies gibt die Umwandlung des
i
aktuellen Index in eine Zahl zurück.Abschließend,
Der Ausdruck
Arbeitet in zwei Teilen:
Der erste Teil erstellt ein Array von 5 undefinierten Elementen. Der zweite geht über dieses Array und nimmt seine Indizes, was zu einem Array von Elementindizes führt:
quelle
ahaExclamationMark.apply(null, Array(2)); //2, true
. Warum zurückgeben2
undtrue
jeweils? Übergeben Sie nicht nur ein Argument, dhArray(2)
hier?apply
, aber dieses Argument wird in zwei Argumente "aufgeteilt", die an die Funktion übergeben werden. Das sehen Sie leichter an den erstenapply
Beispielen. Das ersteconsole.log
zeigt dann, dass wir tatsächlich zwei Argumente erhalten haben (die beiden Array-Elemente), und das zweiteconsole.log
zeigt, dass das Array einekey=>value
Zuordnung im 1. Slot hat (wie im 1. Teil der Antwort erläutert).log.apply(null, document.getElementsByTagName('script'));
nicht erforderlich ist, um in einigen Browsern zu funktionieren, und dass das[].slice.call(NodeList)
Verwandeln einer NodeList in ein Array auch in diesen nicht funktioniert.this
Standardmäßig nurWindow
im nicht strengen Modus.Haftungsausschluss : Dies ist eine sehr formale Beschreibung des obigen Codes - so kann ich ihn erklären. Für eine einfachere Antwort - überprüfen Sie Ziraks großartige Antwort oben. Dies ist eine detailliertere Spezifikation in Ihrem Gesicht und weniger "Aha".
Hier passieren mehrere Dinge. Lassen Sie es uns ein bisschen aufbrechen.
In der ersten Zeile wird der Array-Konstruktor als Funktion mit aufgerufen
Function.prototype.apply
.this
Wertnull
spielt für den Array-Konstruktor keine Rolle (this
ist der gleichethis
wie im Kontext gemäß 15.3.4.3.2.a.new Array
wird das Übergeben eines Objekts mit einerlength
Eigenschaft aufgerufen - das bewirkt, dass dieses Objekt ein Array ist, wie es für alles wichtig ist,.apply
aufgrund der folgenden Klausel in.apply
:.apply
Argumente von 0 an.length
, seit dem Aufruf[[Get]]
auf{ length: 5 }
mit den Werten 0 bis 4 Ausbeutenundefined
des Array - Konstruktor ist mit fünf Argumenten , dessen Wert aufgerufen istundefined
(eine nicht deklarierte Eigenschaft eines Objekts erhalten).var arr = Array.apply(null, { length: 5 });
wird eine Liste mit fünf undefinierten Werten erstellt.Hinweis : Beachten Sie hier den Unterschied zwischen
Array.apply(0,{length: 5})
undArray(5)
, wobei der erste das Fünffache des primitiven Werttyps erstellt und der zweiteundefined
ein leeres Array der Länge 5 erstellt. Insbesondere wegen.map
Werttyps erstellt Verhaltens von (8.b) und der und speziell[[HasProperty]
.Der obige Code in einer kompatiblen Spezifikation ist also der gleiche wie:
Nun zum zweiten Teil.
Array.prototype.map
Ruft die Rückruffunktion (in diesem FallNumber.call
) für jedes Element des Arrays auf und verwendet den angegebenenthis
Wert (in diesem Fall wird diethis
Wert auf `Number gesetzt).Number.call
) ist der Index, und der erste ist dieser Wert.Number
mitthis
asundefined
(dem Array-Wert) und dem Index als Parameter aufgerufen wird. Es ist also im Grunde dasselbe wie die Zuordnung jedesundefined
Arrays zu seinem Array-Index (da der AufrufNumber
eine Typkonvertierung durchführt, in diesem Fall von Nummer zu Nummer, ohne den Index zu ändern).Daher nimmt der obige Code die fünf undefinierten Werte und ordnet sie jeweils ihrem Index im Array zu.
Deshalb erhalten wir das Ergebnis zu unserem Code.
quelle
Array.apply(null, { length: 2 })
und nicht mitArray.apply(null, [2])
demArray
Konstruktor, der2
als Längenwert übergeben wird? GeigeArray.apply(null,[2])
ist so,Array(2)
dass ein leeres Array der Länge 2 erstellt wird und kein Array, das den primitiven Wertundefined
zweimal enthält. Sehen Sie sich meine letzte Bearbeitung in der Notiz nach dem ersten Teil an. Lassen Sie mich wissen, ob es klar genug ist, und wenn nicht, werde ich das klarstellen.{length: 2}
fälscht ein Array mit zwei Elementen, die derArray
Konstruktor in das neu erstellte Array einfügen würde. Da es kein echtes Array gibt, das auf die nicht vorhandenen Elemente zugreift, ergibt sich,undefined
was dann eingefügt wird. Schöner Trick :)Wie Sie sagten, der erste Teil:
Erstellt ein Array mit 5
undefined
Werten.Der zweite Teil ruft die
map
Funktion des Arrays auf, die zwei Argumente akzeptiert und ein neues Array derselben Größe zurückgibt.Das erste Argument, das
map
benötigt wird, ist eine Funktion, die auf jedes Element im Array angewendet werden soll. Es wird erwartet, dass es sich um eine Funktion handelt, die drei Argumente akzeptiert und einen Wert zurückgibt. Beispielsweise:Wenn wir die Funktion foo als erstes Argument übergeben, wird sie für jedes Element mit aufgerufen
Das zweite Argument
map
wird an die Funktion übergeben, die Sie als erstes Argument übergeben. Aber es wäre weder a, b noch c, wennfoo
es so wärethis
.Zwei Beispiele:
und noch eine, um es klarer zu machen:
Was ist also mit Number.call?
Number.call
ist eine Funktion, die 2 Argumente akzeptiert und versucht, das zweite Argument auf eine Zahl zu analysieren (ich bin nicht sicher, was es mit dem ersten Argument macht).Da das zweite übergebene Argument
map
der Index ist, entspricht der Wert, der an diesem Index in das neue Array eingefügt wird, dem Index. Genau wie die Funktionbaz
im obigen Beispiel.Number.call
wird versuchen, den Index zu analysieren - es wird natürlich den gleichen Wert zurückgeben.Das zweite Argument, das Sie
map
in Ihrem Code an die Funktion übergeben haben, wirkt sich nicht auf das Ergebnis aus. Korrigieren Sie mich bitte, wenn ich falsch liege.quelle
Number.call
ist keine spezielle Funktion, die Argumente nach Zahlen analysiert. Es ist einfach=== Function.prototype.call
. Nur das zweite Argument, die Funktion , die als übergeben wird ,this
um -Wertescall
, ist relevant -.map(eval.call, Number)
,.map(String.call, Number)
und.map(Function.prototype.call, Number)
alle sind gleichwertig.Ein Array ist einfach ein Objekt, das das Feld 'Länge' und einige Methoden (z. B. Push) umfasst. Arr in
var arr = { length: 5}
ist also im Grunde dasselbe wie ein Array, in dem die Felder 0..4 den undefinierten Standardwert haben (dharr[0] === undefined
true ergeben).Wie der Name schon sagt, wird im zweiten Teil von einem Array ein neues zugeordnet. Dies geschieht durch Durchlaufen des ursprünglichen Arrays und Aufrufen der Zuordnungsfunktion für jedes Element.
Sie müssen Sie nur noch davon überzeugen, dass das Ergebnis der Mapping-Funktion der Index ist. Der Trick besteht darin, die Methode 'call' (*) zu verwenden, die eine Funktion aufruft, mit der kleinen Ausnahme, dass der erste Parameter als 'this'-Kontext festgelegt ist und der zweite zum ersten Parameter wird (und so weiter). Zufälligerweise ist der zweite Parameter der Index, wenn die Zuordnungsfunktion aufgerufen wird.
Last but not least wird als Methode die Zahl "Klasse" aufgerufen, und wie wir in JS wissen, ist eine "Klasse" einfach eine Funktion, und diese (Zahl) erwartet, dass der erste Parameter der Wert ist.
(*) im Prototyp von Function gefunden (und Number ist eine Funktion).
MASHAL
quelle
[undefined, undefined, undefined, …]
undnew Array(n)
oder{length: n}
- die letzteren sind spärlich , dh sie haben keine Elemente. Dies ist sehr relevant fürmap
, und deshalb wurde die ungeradeArray.apply
verwendet.