Was passiert in diesem Code mit Number-Objekten, die Eigenschaften enthalten und die Nummer erhöhen?

246

Ein kürzlich veröffentlichter Tweet enthielt diesen Ausschnitt aus JavaScript.

Kann jemand bitte Schritt für Schritt erklären, was darin passiert?

> function dis() { return this }
undefined
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
> five * 5
25
> five.wtf
"potato"
> five++
5
> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined
> five
6

Insbesondere ist mir nicht klar:

  • warum das Ergebnis von dis.call(5)a Numbermit einer Art [[PrimitiveValue]]Eigenschaft ist, aber die Ergebnisse von five++und five * 5scheinen nur die einfachen Zahlen 5und 25(nicht Numbers) zu sein
  • warum die five.wtfEigenschaft nach dem five++Inkrement verschwindet
  • warum die five.wtfEigenschaft nach dem five++Inkrement nicht mehr einstellbar ist , obwohl die five.wtf = 'potato?'Zuordnung anscheinend den Wert festlegt.
Nathan Long
quelle
6
Haha, ich habe diesen Tweet gesehen !! Es ist so bizarr, ich denke, weil Sie es multiplizieren, es wirkt sich nicht ganz auf das Objekt aus, ++scheint aber den zugrunde liegenden Typ zu beeinflussen
Callum Linington
8
Welche Zeile hast du nicht verstanden? Prevs PostInkrement? Nach der Multiplikation ist es wieder ein NumberObjekt, das keine wtfEigenschaft hat ... Aber es ist immer noch ein objectdaher kann es haben properties..
Rayon
25
Wenn Sie anrufen dismit dis.call(5), Wraps dies die primitive Zahl 5in ein Objekt vom Typ Number, der das enthält 5, so dass dieses Objekt als zurückgegeben werden kann thisObjekt. Das ++konvertiert es zurück in eine primitive Zahl, die keine Eigenschaften enthalten kann, also wtfbeginnt es undefiniert zu sein.
GSerg
5
@ Rayon ... und das hat sich mit ES6 geändert . In Zukunft wird das Ganze also aufhören zu passieren.
GSerg

Antworten:

278

OP hier. Lustig, dies auf Stack Overflow zu sehen :)

Bevor Sie das Verhalten durchgehen, ist es wichtig, einige Dinge zu klären:

  1. Zahlenwert und Zahlenobjekt ( a = 3vs a = new Number(3)) sind sehr unterschiedlich. Einer ist ein Primitiv, der andere ist ein Objekt. Sie können Grundelementen keine Attribute zuweisen, Objekte jedoch.

  2. Zwang zwischen den beiden ist implizit.

    Beispielsweise:

    (new Number(3) === 3)  // returns false
    (new Number(3) == 3)   // returns true, as the '==' operator coerces
    (+new Number(3) === 3) // returns true, as the '+' operator coerces
  3. Jeder Ausdruck hat einen Rückgabewert. Wenn die REPL einen Ausdruck liest und ausführt, wird dies angezeigt. Die Rückgabewerte bedeuten oft nicht, was Sie denken, und implizieren Dinge, die einfach nicht wahr sind.

Ok, los geht's.

Originalbild des JavaScript-Codes

Das Versprechen.

> function dis() { return this }
undefined
> five = dis.call(5)
[Number: 5]

Definieren Sie eine Funktion disund rufen Sie sie mit auf 5. Dadurch wird die Funktion mit 5als Kontext ( this) ausgeführt. Hier wird es von einem Zahlenwert zu einem Zahlenobjekt gezwungen. Es ist sehr wichtig zu beachten, dass dies nicht geschehen wäre, wenn wir uns im strengen Modus befunden hätten .

> five.wtf = 'potato'
'potato'
> five.wtf
'potato'

Jetzt setzen wir das Attribut five.wtfauf 'potato'und mit fünf als Objekt, sicher genug, dass es die einfache Zuweisung akzeptiert .

> five * 5
25
> five.wtf
'potato'

Mit fiveals Objekt stelle ich sicher, dass es immer noch einfache arithmetische Operationen ausführen kann. Es kann. Bleiben seine Eigenschaften noch bestehen? Ja.

Die Wende.

> five++
5
> five.wtf
undefined

Jetzt überprüfen wir five++. Der Trick beim Postfix-Inkrementieren besteht darin, dass der gesamte Ausdruck anhand des ursprünglichen Werts ausgewertet und dann den Wert erhöht wird. Es sieht aus wie fivenoch fünf, aber wirklich der Ausdruck auf fünf ausgewertet, dann eingestellt fivezu 6.

Es wurde nicht nur fiveauf gesetzt 6, sondern es wurde zurück in einen Zahlenwert gezwungen, und alle Attribute gehen verloren. Da Grundelemente keine Attribute enthalten können, five.wtfist sie undefiniert.

> five.wtf = 'potato?'
'potato?'
> five.wtf
undefined

Ich versuche , wieder ein Attribut zuweisen wtfzu five. Der Rückgabewert impliziert, dass er haftet, aber tatsächlich nicht, weil fivees sich um einen Zahlenwert handelt, nicht um ein Zahlenobjekt. Der Ausdruck wird ausgewertet 'potato?', aber wenn wir überprüfen, sehen wir, dass er nicht zugewiesen wurde.

Das Prestige.

> five
6

Seit dem Postfix-Inkrement fiveist 6.

Matthew
quelle
70
Schaust du genau zu?
Waddles
3
@ Nathan Long Nun, in Java, von dem JavaScript am Anfang stark entlehnt war, haben alle Grundelemente ein Klassenäquivalent. intund Integerzum Beispiel. Ich gehe davon aus, dass Sie auf diese Weise eine Funktion erstellen doSomething(Object)und ihr dennoch Primitive geben können. In diesem Fall werden Grundelemente in die entsprechenden Klassen konvertiert. Aber JS kümmert sich nicht wirklich um Typen, also ist der Grund wahrscheinlich etwas anderes
Suppen
4
@Eric Als erstes ++wenden Sie ToNumber auf den Wert an . Vergleichen Sie einen ähnlichen Fall mit Zeichenfolgen: Wenn ja x="5", wird x++die Zahl zurückgegeben 5.
Apsiller
2
Wirklich, die Magie geschieht alles dis.call(5), der Zwang, Einwände zu erheben, das hätte ich nie erwartet.
McFedr
3
@gman mit der Ausnahme, dass es viele viel detailliertere Einflüsse gibt, einschließlich der Benennung großer Teile seiner Standardbibliothek, des Verhaltens von Standardobjekttypen und sogar der Tatsache, dass eine Klammern- und Semikolonsyntax verwendet wird (selbst wenn die Semikolons vorhanden sind) optional) ergibt sich aus der Tatsache, dass es Java-Programmierern vertraut gemacht wurde. Ja, es ist für Anfänger verwirrend. Das heißt nicht, dass die Einflüsse nicht existieren.
Jules
77

Es gibt zwei verschiedene Möglichkeiten, eine Zahl darzustellen:

var a = 5;
var b = new Number(5);

Das erste ist ein Primitiv, das zweite ein Objekt. In jeder Hinsicht verhalten sich beide gleich, außer dass sie beim Drucken auf der Konsole unterschiedlich aussehen. Ein wichtiger Unterschied besteht darin, dass ein Objekt new Number(5)wie jede Ebene neue Eigenschaften akzeptiert {}, während das Grundelement 5dies nicht tut:

a.foo = 'bar';  // doesn't stick
b.foo = 'bar';  // sticks

Informationen zum ersten dis.call(5)Teil finden Sie unter Wie funktioniert das Schlüsselwort "this"? . Sagen wir einfach, dass das erste Argument to callals Wert von verwendet thiswird und dass diese Operation die Zahl in die komplexere NumberObjektform ++zwingt . * Später zwingt sie sie zurück in die primitive Form, da die Additionsoperation +zu einem neuen Primitiv führt.

> five = dis.call(5)  // for all intents and purposes same as new Number(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"

Ein NumberObjekt akzeptiert neue Eigenschaften.

> five++

++ergibt einen neuen primitiven 6Wert ...

> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined

... die keine benutzerdefinierten Attribute haben und nicht akzeptieren.

* Beachten Sie, dass im strengen Modus das thisArgument anders behandelt und nicht in a konvertiert wird Number. Einzelheiten zur Implementierung finden Sie unter http://es5.github.io/#x10.4.3 .

täuschen
quelle
2
@Pharap sicher viel besser in C / C ++, wo Sie tun können #define true false. Oder in Java, wo Sie einfach neu definieren können, was Zahlen bedeuten. Das sind gute feste Sprachen. In Wahrheit hat jede Sprache ähnliche "Tricks", bei denen Sie ein Ergebnis erhalten, das wie beabsichtigt funktioniert, aber seltsam aussehen kann.
VLAZ
1
@Vld Ok, lassen Sie mich das umformulieren. Deshalb hasse ich es, Enten zu tippen.
Pharap
59

In der JavaScript-Welt gibt es Zwang - eine Detektivgeschichte

Nathan, du hast keine Ahnung, was du entdeckt hast.

Ich habe das jetzt seit Wochen untersucht. Alles begann in einer stürmischen Nacht im letzten Oktober. Ich bin versehentlich auf die NumberKlasse gestoßen - ich meine, warum in aller Welt hatte JavaScript eine NumberKlasse?

Ich war nicht auf das vorbereitet, was ich als nächstes herausfinden würde.

Es stellt sich heraus, dass JavaScript, ohne es Ihnen mitzuteilen, Ihre Zahlen in Objekte und Ihre Objekte in Zahlen direkt unter Ihrer Nase geändert hat.

JavaScript hatte gehofft, niemand würde es verstehen, aber die Leute haben seltsames unerwartetes Verhalten gemeldet, und jetzt habe ich dank Ihnen und Ihrer Frage die Beweise, die ich brauche, um dieses Ding weit aufzublasen.

Das haben wir bisher herausgefunden. Ich weiß nicht, ob ich Ihnen das überhaupt sagen soll - vielleicht möchten Sie Ihr JavaScript ausschalten.

> function dis() { return this }
undefined

Als Sie diese Funktion erstellt haben, hatten Sie wahrscheinlich keine Ahnung, was als nächstes passieren würde. Alles sah gut aus und alles war gut - für jetzt.

Keine Fehlermeldungen, nur das Wort "undefiniert" in der Konsolenausgabe, genau das, was Sie erwarten würden. Immerhin war dies eine Funktionsdeklaration - sie soll nichts zurückgeben.

Dies war jedoch nur der Anfang. Was als nächstes geschah, konnte niemand vorhersagen.

> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Ja, ich weiß, du hast ein erwartet 5, aber das hast du nicht, war es - du hast etwas anderes - etwas anderes.

Mir geht es genauso.

Ich wusste nicht, was ich davon halten sollte. Es hat mich verrückt gemacht. Ich konnte nicht schlafen, ich konnte nicht essen, ich versuchte es wegzutrinken, aber keine Menge Gebirgstau würde mich vergessen lassen. Es ergab einfach keinen Sinn!

Da fand ich heraus, was wirklich los war - es war Zwang, und es geschah genau dort vor meinen Augen, aber ich war zu blind, um es zu sehen.

Mozilla versuchte es zu begraben, indem sie es dort platzierte, wo sie wussten, dass niemand hinschauen würde - in ihrer Dokumentation .

Nach Stunden des rekursiven Lesens und erneuten Lesens und erneuten Lesens fand ich Folgendes:

"... und primitive Werte werden in Objekte konvertiert."

Es war genau dort klar, wie in Open Sans-Schrift geschrieben werden kann. Es war die call()Funktion - wie konnte ich so dumm sein?!

Meine Nummer war überhaupt keine Nummer mehr. In dem Moment, als ich es weitergab call(), wurde es etwas anderes. Es wurde ... ein Objekt.

Ich konnte es zuerst nicht glauben. Wie könnte das wahr sein? Aber ich konnte die Beweise, die sich um mich herum häuften, nicht ignorieren. Es ist genau dort, wenn Sie nur schauen:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

wtfhatte Recht. Zahlen können keine benutzerdefinierten Eigenschaften haben - das wissen wir alle! Es ist das erste, was sie dir an der Akademie beibringen.

Wir hätten es wissen müssen, als wir die Konsolenausgabe sahen - dies war nicht die Zahl, die wir dachten. Dies war ein Betrüger - ein Gegenstand, der sich als unsere süße unschuldige Zahl ausgibt.

Das war ... new Number(5).

Natürlich! Es machte vollkommen Sinn. call()hatte einen Job zu erledigen, er musste eine Funktion aufrufen, und um das zu tun, musste er füllen this, er wusste, dass er das nicht mit einer Nummer machen konnte - er brauchte ein Objekt und er war bereit, alles zu tun, um es zu bekommen, sogar wenn das bedeutete, unsere Nummer zu zwingen. Als er call()die Nummer 5sah, sah er eine Gelegenheit.

Es war der perfekte Plan: Warten Sie, bis niemand mehr hinschaut, und tauschen Sie unsere Nummer gegen ein Objekt aus, das genauso aussieht. Wir bekommen eine Nummer, die Funktion wird aufgerufen und niemand wäre klüger.

Es war wirklich der perfekte Plan, aber wie bei allen Plänen, auch bei den perfekten, war ein Loch darin, und wir wollten gleich hineinfallen.

Sehen Sie, was call()nicht verstanden wurde, war, dass er nicht der einzige in der Stadt war, der Zahlen erzwingen konnte. Das war schließlich JavaScript - Zwang war überall.

call() Ich nahm meine Nummer und wollte nicht aufhören, bis ich die Maske von seinem kleinen Betrüger abzog und ihn der gesamten Stack Overflow-Community aussetzte.

Aber wie? Ich brauchte einen Plan. Sicher, es sieht aus wie eine Zahl, aber ich weiß, dass es nicht so ist. Es muss einen Weg geben, das zu beweisen. Das ist es! Es sieht aus wie eine Zahl, aber kann es sich wie eine verhalten?

Ich sagte, fiveich brauche ihn, um fünfmal größer zu werden - er fragte nicht warum und ich erklärte es nicht. Ich habe dann getan, was jeder gute Programmierer tun würde: Ich habe multipliziert. Sicherlich konnte er sich hier nicht vortäuschen.

> five * 5
25
> five.wtf
'potato'

Verdammt! Nicht nur gut fivemultiplizieren wtfwar noch da. Verdammt dieser Typ und seine Kartoffel.

Was zur Hölle war los? War ich in dieser ganzen Sache falsch? Ist das fivewirklich eine Nummer? Nein, ich muss etwas vermissen, ich weiß es, es gibt etwas, das ich vergessen muss, etwas so Einfaches und Grundlegendes, dass ich es völlig übersehen habe.

Das sah nicht gut aus, ich hatte diese Antwort stundenlang geschrieben und war immer noch nicht näher dran, meinen Standpunkt zu vertreten. Ich konnte nicht so weitermachen, irgendwann hörten die Leute auf zu lesen, ich musste an etwas denken und ich musste schnell daran denken.

Warten Sie, das ist es! fivewar nicht 25, 25 war das Ergebnis, 25 war eine ganz andere Zahl. Natürlich, wie könnte ich das vergessen? Zahlen sind unveränderlich. Wenn Sie multiplizieren, wird 5 * 5nichts zugewiesen. Sie erstellen einfach eine neue Zahl25 .

Das muss hier passieren. Irgendwie , wenn ich mehrfach five * 5, fivemuss immer in eine Reihe gezwungen und diese Zahl muss der eine für die Multiplikation verwendet werden. Es sind die Ergebnisse dieser Multiplikation, die auf der Konsole gedruckt werden, nicht der Wert von sich fiveselbst. fivebekommt nie etwas zugewiesen - also ändert es sich natürlich nicht.

Wie kann ich mir dann fivedas Ergebnis einer Operation zuordnen? Ich habe es verstanden. Bevor fiveich überhaupt nachdenken konnte, schrie ich "++".

> five++
5

Aha! Ich hatte ihn! Jeder weiß , 5 + 1ist 6, war dies der Beweis Ich musste, dass er fivekeine Zahl war! Es war ein Betrüger! Ein schlechter Betrüger, der nicht zählen konnte. Und ich könnte es beweisen. So verhält sich eine reelle Zahl:

> num = 5
5
> num++
5

Warten? Was war hier los? seufz Ich war so in das Busting verwickelt, fivedass ich vergesse, wie Postbetreiber arbeiten. Wenn ich ++am Ende von fivesage, gebe ich den aktuellen Wert zurück und erhöhe ihn dann five. Es ist der Wert vor dem Vorgang, der auf der Konsole gedruckt wird. numwar in der Tat 6und ich konnte es beweisen:

>num
6

Zeit zu sehen, was fivewirklich war:

>five
6

... es war genau das, was es sein sollte. fivewar gut - aber ich war besser. Wenn fivees immer noch ein Objekt wäre, würde das bedeuten, dass es immer noch das Eigentum hätte wtfund ich wäre bereit, alles zu wetten, was es nicht tat.

> five.wtf
undefined

Aha! Ich lag richtig. Ich hatte ihn! fivewar jetzt eine Nummer - es war kein Objekt mehr. Ich wusste, dass der Multiplikationstrick es diesmal nicht retten würde. Sehen five++ist wirklich five = five + 1. Im Gegensatz zur Multiplikation weist der ++Operator einen Wert zu five. Insbesondere weist es ihm die Ergebnisse zu, von five + 1denen genau wie im Fall der Multiplikation eine neue unveränderliche Zahl zurückgegeben wird .

Ich wusste, dass ich ihn hatte und nur um sicherzugehen, dass er sich nicht herauswinden konnte. Ich hatte noch einen Test im Ärmel. Wenn ich recht hätte und fivejetzt wirklich eine Nummer wäre, würde das nicht funktionieren:

> five.wtf = 'potato?'
'potato?'

Diesmal würde er mich nicht täuschen. Ich wusste potato?, dass es auf der Konsole gedruckt werden würde, da dies die Ausgabe der Aufgabe ist. Die eigentliche Frage ist, wird es wtfnoch da sein?

> five.wtf
undefined

Genau wie ich vermutet habe - nichts - weil Nummern keine Eigenschaften zugewiesen werden können. Das haben wir im ersten Jahr an der Akademie gelernt;)

Danke Nathan. Dank Ihres Mutes, diese Frage zu stellen, kann ich das alles endlich hinter mich bringen und zu einem neuen Fall übergehen.

Wie dieser über die Funktion toValue(). Oh mein Gott. Nein!

Luis Perez
quelle
9
Vergiss es Jake; Es ist Javascript.
Seth
Warum nennen wir Konstruktorfunktionen in JS "Klassen"?
evolutionxbox
27
01 > function dis() { return this }
02 undefined
03 > five = dis.call(5)
04 Number {[[PrimitiveValue]]: 5}
05 > five.wtf = 'potato'
06 "potato"
07 > five.wtf
08 "potato"
09 > five * 5
10 25
11 > five.wtf
12 "potato"
13 > five++
14 5
15 > five.wtf
16 undefined
17 > five.wtf = 'potato?'
18 "potato?"
19 > five.wtf
20 undefined
21 > five
22 6

01deklariert eine Funktion dis, die das Kontextobjekt zurückgibt. Was thisÄnderungen darstellt, hängt davon ab, ob Sie den strengen Modus verwenden oder nicht. Das gesamte Beispiel hat unterschiedliche Ergebnisse, wenn die Funktion wie folgt deklariert wurde:

> function dis() { "use strict"; return this }

Dies wird in Abschnitt 10.4.3 in der ES5-Spezifikation beschrieben

  1. Wenn der Funktionscode ein strenger Code ist, setzen Sie ThisBinding auf thisArg.
  2. Andernfalls setzen Sie die ThisBinding auf das globale Objekt, wenn thisArg null oder undefiniert ist.
  3. Andernfalls, wenn Type (thisArg) kein Object ist, setzen Sie ThisBinding auf ToObject (thisArg).

02ist der Rückgabewert der Funktionsdeklaration. undefinedsollte hier selbsterklärend sein.

03Die Variable fivewird mit dem Rückgabewert initialisiert, diswenn sie im Kontext des primitiven Werts aufgerufen wird 5. Da dissich diese Leitung nicht im strengen Modus befindet, ist sie mit dem Anrufen identisch five = Object(5).

04Der ungerade Number {[[PrimitiveValue]]: 5}Rückgabewert ist die Darstellung des Objekts, das den primitiven Wert umschließt5

05fiveDer wtfEigenschaft des Objekts wird ein Zeichenfolgenwert von zugewiesen'potato'

06 ist der Rückgabewert der Zuordnung und sollte selbsterklärend sein.

07fiveDie wtfEigenschaft des Objekts wird untersucht

08wie five.wtfzuvor eingestellt, 'potato'kehrt es 'potato'hier zurück

09Das fiveObjekt wird mit dem Grundwert multipliziert 5. Dies unterscheidet sich nicht von anderen Objekten, die multipliziert werden, und wird in Abschnitt 11.5 der ES5-Spezifikation erläutert . Besonders hervorzuheben ist, wie Objekte in numerische Werte umgewandelt werden, was in einigen Abschnitten behandelt wird.

9.3 ToNumber :

  1. Sei primValue ToPrimitive (Eingabeargument, Hinweisnummer).
  2. Return ToNumber (primValue).

9.1 ToPrimitive :

Gibt einen Standardwert für das Objekt zurück. Der Standardwert eines Objekts wird durch Aufrufen der internen Methode [[DefaultValue]] des Objekts abgerufen, wobei der optionale Hinweis PreferredType übergeben wird. Das Verhalten der internen Methode [[DefaultValue]] wird durch diese Spezifikation für alle nativen ECMAScript-Objekte in 8.12.8 definiert .

8.12.8 [[DefaultValue]] :

Sei valueOf das Ergebnis des Aufrufs der internen Methode [[Get]] von Objekt O mit dem Argument "valueOf".

  1. Wenn IsCallable (valueOf) wahr ist, dann

    1. Sei val das Ergebnis des Aufrufs der internen Methode [[Call]] von valueOf mit O als diesem Wert und einer leeren Argumentliste.
    2. Wenn val ein primitiver Wert ist, geben Sie val zurück.

Dies ist alles ein Umweg, um zu sagen, dass die valueOfFunktion des Objekts aufgerufen wird und der Rückgabewert dieser Funktion in der Gleichung verwendet wird. Wenn Sie die valueOfFunktion ändern, können Sie die Ergebnisse der Operation ändern:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50

10wie fives valueOfFunktion war unverändert, es gibt den eingewickelt Grundwert , 5so dass five * 5auswertet zu 5 * 5denen Ergebnisse in25

11fiveDie wtfEigenschaft des Objekts wird erneut ausgewertet , obwohl sie seit ihrer Zuweisung unverändert geblieben ist 05.

12 'potato'

13Der Postfix Increment Operator wird aufgerufen. Er ruft fiveden numerischen Wert ab ( 5wir haben bereits erläutert, wie früher), speichert den Wert, damit er zurückgegeben werden kann, addiert ihn 1zum Wert ( 6), weist den Wert zu fiveund gibt den gespeicherten Wert zurück ( 5)

14 Nach wie vor ist der zurückgegebene Wert der Wert, bevor er erhöht wurde

15Auf die wtfEigenschaft des 6in der Variablen gespeicherten primitiven Werts ( ) fivewird zugegriffen. Abschnitt 15.7.5 der ES5-Spezifikation definiert dieses Verhalten. Zahlen erhalten die Eigenschaften von Number.prototype.

16 Number.prototypehat keine wtfEigenschaft, wird also undefinedzurückgegeben

17 five.wtfwird ein Wert von zugewiesen 'potato?'. Die Zuordnung ist in 11.13.1 der ES5-Spezifikation definiert . Grundsätzlich wird der zugewiesene Wert zurückgegeben, aber nicht gespeichert.

18 'potato?' wurde vom Zuweisungsoperator zurückgegeben

19Auch hier fivewird auf den Wert von 6zugegriffen, und es wird erneut Number.prototypekeine wtfEigenschaft angegeben

20 undefined wie oben erklärt

21 five wird zugegriffen

22 6 wird wie in erklärt zurückgegeben 13

zzzzBov
quelle
17

Es ist ziemlich einfach.

function dis () { return this; }

Dies gibt den thisKontext zurück. Wenn Sie dies tun, übergeben call(5)Sie die Nummer als Objekt.

Die callFunktion liefert keine Argumente. Das erste Argument, das Sie angeben, ist der Kontext von this. Wenn Sie es im Kontext haben möchten, geben Sie es normalerweise {}so an dis.call({}), was bedeutet, dass thisdie Funktion leer ist this. Wenn Sie jedoch bestehen, wird 5es anscheinend in ein Objekt konvertiert. Siehe .call

Die Rückkehr ist also object

Wenn Sie dies tun five * 5, sieht JavaScript das Objekt fiveals primitiven Typ und ist daher äquivalent zu 5 * 5. Interessanterweise ist '5' * 5es immer noch gleich 25, so dass JavaScript eindeutig unter die Haube wirft. In fivedieser Zeile werden keine Änderungen am zugrunde liegenden Typ vorgenommen

Wenn Sie dies ++jedoch tun , wird das Objekt in den primitiven numberTyp konvertiert, wodurch die .wtfEigenschaft entfernt wird. Weil Sie den zugrunde liegenden Typ beeinflussen

Callum Linington
quelle
Die Rückgabe ist Nummer .
GSerg
++konvertiert und weist es zurück. ++ist gleich variable = variable + 1. Sie verlieren alsowtf
Rajesh
Das *vs ++verwirrt mich überhaupt nicht. Eine Funktion zu haben, die keine Argumente erwartet und zurückgibt this, diese Funktion trotzdem ein Argument akzeptieren zu lassen und etwas zurückzugeben, das dem Argument ähnelt, aber nicht genau ist - das macht für mich keinen Sinn.
Nathan Long
Es wurde @NathanLong aktualisiert, um zu zeigen, was dis.call()funktioniert.
Callum Linington
5 ++ verhält sich genauso wie in C, also nichts überraschendes. thisist einfach ein Zeiger auf ein Objekt, sodass primitive Typen implizit konvertiert werden. Warum konnte eine Funktion ohne Argumente nicht mindestens einen Kontext haben? Das erste Argument von calloder bindwird verwendet, um den Kontext festzulegen. Außerdem sind Funktionen Schließungen, was bedeutet, dass sie Zugriff auf mehr als nurarguments
Rivenfall
10

Die primitiven Werte können keine Eigenschaft haben. Wenn Sie jedoch versuchen, auf eine Eigenschaft mit primitivem Wert zuzugreifen, wird sie transparent in ein temporäres Number-Objekt umgewandelt.

So:

> function dis() { return this }
undefined
// Like five.dis(), so dis return the temporaty Number object and 
// reference it in five
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

// Write the wtf attribut on the Number object referenced by five
> five.wtf = 'potato'
"potato"
// Read the wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Return 5*5 but dont change the reference of five
> five * 5
25
// Read the same wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Change the five reference to a new primitive value (5+1). Five
// reference a primitive now.
> five++
5

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined

// Write the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. But this object not referenced by
// five. It will be lost.
> five.wtf = 'potato?'
"potato?"

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined
> five
6
Techniv
quelle
8

Funktion deklarieren dis. Die Funktion gibt ihren Kontext zurück

function dis() { return this }
undefined

disMit Kontext anrufen 5. Primitive Werte werden eingerahmt, wenn sie im strengen Modus ( MDN ) als Kontext übergeben werden . Also fivejetzt ist Objekt (Boxnummer).

five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Deklarieren Sie die wtfEigenschaft für die fiveVariable

five.wtf = 'potato'
"potato"

Wert von five.wtf

five.wtf
"potato"

fiveist eingerahmt 5, also ist es Nummer und Objekt gleichzeitig (5 * 5 = 25). Es ändert sich nicht five.

five * 5
25

Wert von five.wtf

five.wtf
"potato"

Hier auspacken five. fivejetzt ist nur primitiv number. Er druckt 5, und fügen Sie dann 1zu five.

five++
5

fiveist 6jetzt primitive Zahl , es gibt keine Eigenschaften darin.

five.wtf
undefined

Grundelemente können keine Eigenschaften haben, Sie können dies nicht festlegen

five.wtf = 'potato?'
"potato?"

Sie können dies nicht lesen, da es nicht eingestellt wurde

five.wtf
undefined

fiveliegt an 6der obigen Inkrementierung des Beitrags

five
6
Maxx
quelle
7

Zunächst sieht es so aus, als würde dies über die NodeJS-Konsole ausgeführt.

1.

    function dis() { return this }

Erstellt die Funktion dis (), aber da sie nicht als a festgelegt wurde, vargab es keinen Wert, der zurückgegeben werden konnte, ebenso undefinedwie die Ausgabe, obwohl sie dis()definiert wurde. Auf einer Nebenbemerkung thiswurde nicht zurückgegeben, weil die Funktion nicht ausgeführt wurde.

2.

    five = dis.call(5)

Diese kehrt Javascript ist NumberObjekt , weil Sie nur die Funktion einstellen dis()‚s thisWert auf den ursprünglichen fünf.

3.

   five.wtf = 'potato'

Der erste kehrt zurück, "potato"weil Sie gerade die Eigenschaft wtfvon fiveauf gesetzt haben 'potato'. Javascript gibt den Wert einer von Ihnen festgelegten Variablen zurück, sodass Sie auf einfache Weise mehrere Variablen verketten und auf denselben Wert wie folgt setzen können : a = b = c = 2.

4.

    five * 5

Dies gibt , 25weil Sie multipliziert einfach die primitive Zahl 5zu five. Der Wert von fivewurde durch den Wert des NumberObjekts bestimmt.

5.

    five.wtf

Ich habe diese Zeile zuvor übersprungen, weil ich sie hier wiederholen würde. Es wird nur der Wert der Eigenschaft zurückgegeben wtf, die Sie oben festgelegt haben.

6.

    five++

Wie @Callum sagte, ++wird der Typ numbervom Objekt in denselben Wert konvertiert Number {[[PrimitiveValue]]: 5}}.

Jetzt, weil fivees a ist number, können Sie keine Eigenschaften mehr festlegen, bis Sie so etwas tun:

    five = dis.call(five)
    five.wtf = "potato?"

oder

    five = { value: 6, wtf: "potato?" }

Beachten Sie auch, dass der zweite Weg ein anderes Verhalten aufweist als die erste Methode, da anstelle des Numberzuvor erstellten Objekts ein generisches Objekt definiert wird .

Ich hoffe, das hilft, Javascript nimmt gerne Dinge an, so dass es verwirrend werden kann, wenn man vom NumberObjekt zum Grundelement wechselt number. Sie können überprüfen, um welchen Typ es sich handelt, indem Sie das typeofSchlüsselwort verwenden. Geben Sie den Typ 5 ein, nachdem Sie ihn initialisiert haben 'object', und nachdem Sie five++ihn zurückgegeben haben 'number'.

@deceze beschreibt den Unterschied zwischen dem Number-Objekt und der primitiven Zahl sehr gut.

Patrick Barr
quelle
6

JavaScript-Bereiche bestehen aus Ausführungskontexten. Jeder Ausführungskontext verfügt über eine lexikalische Umgebung (externe / global begrenzte Werte), eine variable Umgebung (lokal begrenzte Werte) und eine diese Bindung .

Die diese Bindung ist ein sehr wichtiger Teil des Ausführungskontextes. Die Verwendung callist eine Möglichkeit, diese Bindung zu ändern. Dadurch wird automatisch ein Objekt erstellt, mit dem die Bindung gefüllt wird.

Function.prototype.call () (von MDN)

Syntax
fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg
Der Wert, der für den Aufruf zum Spaß bereitgestellt wird. Beachten Sie, dass dies möglicherweise nicht der tatsächliche Wert ist, den die Methode sieht: Wenn die Methode eine Funktion im nicht strengen Moduscode ist, werden null und undefiniert durch das globale Objekt ersetzt und primitive Werte in Objekte konvertiert . (Hervorhebung von mir)

Sobald es offensichtlich ist, dass 5 in umgewandelt wird new Number(5), sollte der Rest ziemlich offensichtlich sein. Beachten Sie, dass andere Beispiele ebenfalls funktionieren, solange es sich um primitive Werte handelt.

function primitiveToObject(prim){
  return dis.call(prim);
}
function dis(){ return this; }

//existing example
console.log(primitiveToObject(5));

//Infinity
console.log(primitiveToObject(1/0));

//bool
console.log(primitiveToObject(1>0));

//string
console.log(primitiveToObject("hello world"));
<img src="http://i.stack.imgur.com/MUyRV.png" />

Geben Sie hier die Bildbeschreibung ein

Travis J.
quelle
4

Einige Konzepte erklären, was passiert

5 ist eine Zahl, ein primitiver Wert

Number {[[PrimitiveValue]]: 5} ist eine Instanz von Number (nennen wir es Object Wrapper)

Wenn Sie über einen primitiven Wert auf eine Eigenschaft / Methode zugreifen, erstellt die JS-Engine einen Objekt-Wrapper des entsprechenden Typs ( Numberfür 5, Stringfür 'str'und Booleanfür true) und löst den Eigenschaftszugriff / Methodenaufruf für diesen Objekt-Wrapper auf. Dies passiert true.toString()zum Beispiel , wenn Sie dies tun .

Wenn Sie Operationen an Objekten ausführen, werden diese (mit toStringoder valueOf) in primitive Werte konvertiert , um diese Operationen aufzulösen - beispielsweise wenn Sie dies tun

var obj = { a : 1 };
var string = 'mystr' + obj;
var number = 3 + obj;

stringenthält die Zeichenfolgenverkettung von mystrund obj.toString()und enthält numberdie Hinzufügung von 3und obj.valueOf().

Nun, um alles zusammenzufügen

five = dis.call(5)

dis.call(5)verhält sich so, als (5).dis()hätte man 5tatsächlich die Methode dis. Um den Methodenaufruf aufzulösen, wird der Objekt-Wrapper erstellt und der Methodenaufruf darauf aufgelöst. Zu diesem Zeitpunkt zeigt fünf auf einen Objekt-Wrapper um den Grundwert 5.

five.wtf = 'potato'

Das Festlegen einer Eigenschaft für ein Objekt ist hier nichts Besonderes.

five * 5

Dadurch wird five.valueOf() * 5der primitive Wert tatsächlich vom Objekt-Wrapper abgerufen. fivezeigt immer noch auf das ursprüngliche Objekt.

five++

Das ist eigentlich five = five.valueOf() + 1. Vor dieser Zeile befindet sich fiveder Objekt-Wrapper um den Wert 5, während nach diesem Punkt fiveder primitive Wert enthalten ist6 .

five.wtf
five.wtf = 'potato?'
five.wtf

fiveist kein Objekt mehr. Jede dieser Zeilen erstellt eine neue Instanz von Number, um den .wtfEigenschaftszugriff aufzulösen . Die Instanzen sind unabhängig, sodass das Festlegen einer Eigenschaft für eine Instanz für eine andere nicht sichtbar ist. Der Code entspricht vollständig diesem:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;
Tibos
quelle