Der 'Wat'-Vortrag für CodeMash 2012 weist im Grunde genommen auf einige bizarre Macken mit Ruby und JavaScript hin.
Ich habe eine JSFiddle der Ergebnisse unter http://jsfiddle.net/fe479/9/ erstellt .
Die für JavaScript spezifischen Verhaltensweisen (da ich Ruby nicht kenne) sind unten aufgeführt.
Ich habe in der JSFiddle festgestellt, dass einige meiner Ergebnisse nicht mit denen im Video übereinstimmen, und ich bin mir nicht sicher, warum. Ich bin jedoch gespannt, wie JavaScript jeweils hinter den Kulissen arbeitet.
Empty Array + Empty Array
[] + []
result:
<Empty String>
Ich bin ziemlich neugierig auf den +
Operator, wenn er mit Arrays in JavaScript verwendet wird. Dies entspricht dem Ergebnis des Videos.
Empty Array + Object
[] + {}
result:
[Object]
Dies entspricht dem Ergebnis des Videos. Was ist denn hier los? Warum ist das ein Objekt? Was macht der +
Bediener?
Object + Empty Array
{} + []
result:
[Object]
Dies stimmt nicht mit dem Video überein. Das Video legt nahe, dass das Ergebnis 0 ist, während ich [Objekt] erhalte.
Object + Object
{} + {}
result:
[Object][Object]
Dies stimmt auch nicht mit dem Video überein. Wie führt die Ausgabe einer Variablen zu zwei Objekten? Vielleicht ist meine JSFiddle falsch.
Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
Wat + 1 führt zu wat1wat1wat1wat1
...
Ich vermute, dass dies nur ein einfaches Verhalten ist, bei dem der Versuch, eine Zahl von einer Zeichenfolge zu subtrahieren, zu NaN führt.
quelle
Array(16).join("wat" - 1) + " Batman!"
{} + {}
.Antworten:
Hier ist eine Liste mit Erklärungen für die Ergebnisse, die Sie sehen (und sehen sollen). Die Referenzen, die ich verwende, stammen aus dem ECMA-262-Standard .
[] + []
Bei Verwendung des Additionsoperators werden sowohl der linke als auch der rechte Operand zuerst in Grundelemente konvertiert ( §11.6.1 ). Wie pro §9.1 , ein Objekt , Umwandeln (in diesem Fall ein Array) auf einen primitiven kehrt seinen Standardwert, der für Objekte mit einer gültigen
toString()
Methode das Ergebnis des Aufrufs istobject.toString()
( §8.12.8 ). Für Arrays entspricht dies dem Aufrufarray.join()
( §15.4.4.2 ). Das Verbinden eines leeren Arrays führt zu einer leeren Zeichenfolge. Schritt 7 des Additionsoperators gibt also die Verkettung von zwei leeren Zeichenfolgen zurück, bei denen es sich um die leere Zeichenfolge handelt.[] + {}
Ähnlich wie bei
[] + []
werden beide Operanden zuerst in Grundelemente konvertiert. Für "Objektobjekte" (§15.2) ist dies wiederum das Ergebnis des Aufrufsobject.toString()
, was für nicht null, nicht undefinierte Objekte"[object Object]"
( §15.2.4.2 ) ist.{} + []
Das
{}
hier wird nicht als Objekt analysiert, sondern als leerer Block ( §12.1 , zumindest solange Sie diese Anweisung nicht als Ausdruck erzwingen, sondern dazu später mehr). Der Rückgabewert von leeren Blöcken ist leer, daher ist das Ergebnis dieser Anweisung dasselbe wie+[]
. Der unäre+
Operator ( §11.4.6 ) kehrt zurückToNumber(ToPrimitive(operand))
. Wie wir bereits wissen,ToPrimitive([])
ist die leere Zeichenkette, und nach §9.3.1 ,ToNumber("")
ist 0.{} + {}
Ähnlich wie im vorherigen Fall wird der erste
{}
als Block mit leerem Rückgabewert analysiert. Wieder+{}
ist das gleiche wieToNumber(ToPrimitive({}))
undToPrimitive({})
ist"[object Object]"
(siehe[] + {}
). Um das Ergebnis von zu erhalten+{}
, müssen wirToNumber
auf die Zeichenfolge anwenden"[object Object]"
. Wenn wir die Schritte aus §9.3.1 befolgen , erhalten wirNaN
als Ergebnis:Array(16).join("wat" - 1)
Wie pro §15.4.1.1 und §15.4.2.2 ,
Array(16)
ein neues Array mit einer Länge erzeugt 16. Um den Wert des Arguments zu erhalten zu verbinden, §11.6.2 Schritte # 5 und 6 zeigt # , dass wir beiden Operanden in einem konvertieren Nummer mitToNumber
.ToNumber(1)
ist einfach 1 ( §9.3 ), währendToNumber("wat")
wiederumNaN
gemäß §9.3.1 . Nach dem Schritt 7 von §11.6.2 , §11.6.3 diktiert , dassDas Argument dazu
Array(16).join
ist alsoNaN
. Nach §15.4.4.5 (Array.prototype.join
) müssen wirToString
auf das Argument"NaN"
( §9.8.1 ) zurückgreifen :Nach Schritt 10 von §15.4.4.5 erhalten wir 15 Wiederholungen der Verkettung von
"NaN"
und der leeren Zeichenfolge, was dem angezeigten Ergebnis entspricht. Bei Verwendung von"wat" + 1
anstelle von"wat" - 1
als Argument wird der Additionsoperator1
in eine Zeichenfolge konvertiert , anstatt"wat"
in eine Zahl zu konvertieren , sodass er effektiv aufruftArray(16).join("wat1")
.Warum Sie für den
{} + []
Fall unterschiedliche Ergebnisse sehen : Wenn Sie es als Funktionsargument verwenden, erzwingen Sie, dass die Anweisung ein ExpressionStatement ist , wodurch es unmöglich wird, sie{}
als leeren Block zu analysieren , sodass sie stattdessen als leeres Objekt analysiert wird wörtlich.quelle
[]+1
folgt so ziemlich der gleichen Logik wie[]+[]
, nur mit1.toString()
als rhs-Operand. Für[]-1
sieht die Erklärung"wat"-1
in Punkt 5 , dass Denken Sie daran ,ToNumber(ToPrimitive([]))
ist 0 (Punkt 3).Dies ist eher ein Kommentar als eine Antwort, aber aus irgendeinem Grund kann ich Ihre Frage nicht kommentieren. Ich wollte Ihren JSFiddle-Code korrigieren. Ich habe dies jedoch in den Hacker News gepostet und jemand hat vorgeschlagen, es hier erneut zu veröffentlichen.
Das Problem im JSFiddle-Code besteht darin, dass
({})
(Öffnen von Klammern in Klammern) nicht dasselbe ist wie{}
(Öffnen von Klammern als Anfang einer Codezeile). Wenn Sie also tippenout({} + [])
, zwingen Sie das{}
, etwas zu sein, was es nicht ist, wenn Sie tippen{} + []
. Dies ist Teil der allgemeinen Wasserqualität von Javascript.Die Grundidee war einfaches JavaScript, das beide Formen zulassen wollte:
Zu diesem Zweck wurden zwei Interpretationen der Öffnungsklammer vorgenommen: 1. Sie ist nicht erforderlich und 2. Sie kann überall auftreten .
Dies war ein falscher Schritt. Echter Code hat keine öffnende Klammer mitten im Nirgendwo, und realer Code ist auch fragiler, wenn er die erste Form anstelle der zweiten verwendet. (Ungefähr alle zwei Monate bei meinem letzten Job wurde ich an den Schreibtisch eines Kollegen gerufen, wenn die Änderungen an meinem Code nicht funktionierten, und das Problem war, dass sie dem "Wenn" eine Zeile hinzugefügt hatten, ohne Curly hinzuzufügen Ich habe mir schließlich angewöhnt, dass die geschweiften Klammern immer erforderlich sind, auch wenn Sie nur eine Zeile schreiben.)
Glücklicherweise repliziert eval () in vielen Fällen die volle Leistung von JavaScript. Der JSFiddle-Code sollte lauten:
[Außerdem ist es das erste Mal seit vielen, vielen Jahren, dass ich document.writeln geschrieben habe, und ich fühle mich ein wenig schmutzig, wenn ich etwas schreibe, das sowohl document.writeln () als auch eval () betrifft.]
quelle
This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere
- Ich bin anderer Meinung (irgendwie): Ich habe in der Vergangenheit oft solche Blöcke verwendet, um Variablen in C zu erfassen . Diese Angewohnheit wurde vor einiger Zeit beim Einbetten von C aufgegriffen, bei dem Variablen auf dem Stapel Speicherplatz beanspruchen. Wenn sie also nicht mehr benötigt werden, möchten wir, dass der Speicherplatz am Ende des Blocks freigegeben wird. ECMAScript umfasst jedoch nur Bereiche innerhalb von function () {} -Blöcken. Obwohl ich nicht der Meinung bin, dass das Konzept falsch ist, stimme ich zu, dass die Implementierung in JS ( möglicherweise ) falsch ist.let
Variablen mit Blockbereich deklarieren.Ich stimme der Lösung von @ Ventero zu. Wenn Sie möchten, können Sie detaillierter darauf eingehen, wie
+
die Operanden konvertiert werden.Erster Schritt (§9.1): konvertiert beide Operanden in Primitiven (primitive Werte sind
undefined
,null
, booleans, Zahlen, Strings, alle anderen Werte sind Objekte, einschließlich Arrays und Funktionen). Wenn ein Operand bereits primitiv ist, sind Sie fertig. Wenn nicht, handelt es sich um ein Objekt,obj
und die folgenden Schritte werden ausgeführt:obj.valueOf()
. Wenn es ein Grundelement zurückgibt, sind Sie fertig. Direkte Instanzen vonObject
und Arrays geben sich selbst zurück, sodass Sie noch nicht fertig sind.obj.toString()
. Wenn es ein Grundelement zurückgibt, sind Sie fertig.{}
und[]
beide geben einen String zurück, damit Sie fertig sind.TypeError
.Für Daten werden Schritt 1 und 2 vertauscht. Sie können das Konvertierungsverhalten wie folgt beobachten:
Interaktion (
Number()
zuerst in primitiv, dann in Zahl konvertiert):Zweiter Schritt (§11.6.1): Wenn einer der Operanden eine Zeichenfolge ist, wird der andere Operand ebenfalls in eine Zeichenfolge konvertiert und das Ergebnis durch Verketten von zwei Zeichenfolgen erzeugt. Andernfalls werden beide Operanden in Zahlen konvertiert und das Ergebnis durch Hinzufügen erzeugt.
Detailliertere Erläuterung des Konvertierungsprozesses: „ Was ist {} + {} in JavaScript? ”
quelle
Wir können uns auf die Spezifikation beziehen und das ist großartig und am genauesten, aber die meisten Fälle können auch mit den folgenden Aussagen verständlicher erklärt werden:
+
und-
Operatoren arbeiten nur mit primitiven Werten. Insbesondere+
funktioniert (Addition) entweder mit Zeichenfolgen oder Zahlen, und+
(unär) und-
(Subtraktion und unär) funktionieren nur mit Zahlen.valueOf
odertoString
, die für jedes Objekt verfügbar sind. Dies ist der Grund, warum solche Funktionen oder Operatoren beim Aufrufen von Objekten keine Fehler auslösen.Also können wir das sagen:
[] + []
ist das gleiche wieString([]) + String([])
das gleiche wie'' + ''
. Ich habe oben erwähnt, dass+
(Addition) auch für Zahlen gültig ist, aber es gibt keine gültige Zahlendarstellung eines Arrays in JavaScript, daher wird stattdessen das Hinzufügen von Zeichenfolgen verwendet.[] + {}
ist das gleiche wieString([]) + String({})
das gleiche wie'' + '[object Object]'
{} + []
. Dieser verdient mehr Erklärung (siehe Antwort von Ventero). In diesem Fall werden geschweifte Klammern nicht als Objekt, sondern als leerer Block behandelt, sodass sich herausstellt, dass es dasselbe ist wie+[]
. Unary+
funktioniert nur mit Zahlen, daher versucht die Implementierung, eine Zahl herauszuholen[]
. Zuerst wird versucht,valueOf
welches bei Arrays dasselbe Objekt zurückgibt, und dann wird der letzte Ausweg versucht: die Umwandlung einestoString
Ergebnisses in eine Zahl. Wir können es so schreiben, wie+Number(String([]))
es dasselbe ist wie+Number('')
was dasselbe ist wie+0
.Array(16).join("wat" - 1)
Die Subtraktion-
funktioniert nur mit Zahlen, ist also dasselbe wie :Array(16).join(Number("wat") - 1)
, da"wat"
sie nicht in eine gültige Zahl umgewandelt werden kann. Wir erhaltenNaN
und jede arithmetische Operation aufNaN
Ergebnisse mitNaN
, also haben wir :Array(16).join(NaN)
.quelle
Um zu stützen, was früher geteilt wurde.
Die zugrunde liegende Ursache für dieses Verhalten ist teilweise auf die schwach typisierte Natur von JavaScript zurückzuführen. Beispielsweise ist der Ausdruck 1 + "2" nicht eindeutig, da es zwei mögliche Interpretationen gibt, die auf den Operandentypen (int, string) und (int int) basieren:
Somit erhöhen sich mit unterschiedlichen Eingabetypen die Ausgabemöglichkeiten.
Der Additionsalgorithmus
Die JavaScript-Grundelemente sind string, number, null, undefined und boolean (Symbol wird in Kürze in ES6 verfügbar sein). Jeder andere Wert ist ein Objekt (z. B. Arrays, Funktionen und Objekte). Der Zwangsprozess zum Umwandeln von Objekten in primitive Werte wird folgendermaßen beschrieben:
Wenn beim Aufrufen von object.valueOf () ein primitiver Wert zurückgegeben wird, geben Sie diesen Wert zurück, andernfalls fahren Sie fort
Wenn beim Aufrufen von object.toString () ein primitiver Wert zurückgegeben wird, geben Sie diesen Wert zurück, andernfalls fahren Sie fort
Wirf einen TypeError
Hinweis: Bei Datumswerten muss die Reihenfolge toString vor valueOf aufgerufen werden.
Wenn ein Operandenwert eine Zeichenfolge ist, führen Sie eine Zeichenfolgenverkettung durch
Andernfalls konvertieren Sie beide Operanden in ihren numerischen Wert und fügen Sie diese Werte hinzu
Wenn Sie die verschiedenen Zwangswerte von Typen in JavaScript kennen, werden die verwirrenden Ausgaben klarer. Siehe die Zwangstabelle unten
Es ist auch gut zu wissen, dass der JavaScript-Operator + linksassoziativ ist, da dies bestimmt, welche Ausgabe Fälle mit mehr als einer + Operation sein werden.
Wenn Sie also 1 + "2" verwenden, erhalten Sie "12", da bei jeder Addition mit einer Zeichenfolge standardmäßig die Zeichenfolgenverkettung verwendet wird.
Weitere Beispiele finden Sie in diesem Blog-Beitrag (Haftungsausschluss, den ich geschrieben habe).
quelle