782 Stimmen

Was ist die Erklärung für diese bizarren JavaScript-Verhaltensweisen, die im "Wat"-Vortrag für CodeMash 2012 erwähnt werden?

En Wat"-Vortrag für CodeMash 2012 weist grundsätzlich auf ein paar bizarre Eigenheiten von Ruby und JavaScript hin.

Ich habe ein JSFiddle mit den Ergebnissen erstellt unter http://jsfiddle.net/fe479/9/ .

Die für JavaScript spezifischen Verhaltensweisen (da ich Ruby nicht kenne) sind unten aufgeführt.

Ich habe im JSFiddle festgestellt, dass einige meiner Ergebnisse nicht mit denen im Video übereinstimmen, und ich bin mir nicht sicher, warum. Ich bin jedoch neugierig, wie JavaScript in jedem Fall hinter den Kulissen arbeitet.

Empty Array + Empty Array
[] + []
result:
<Empty String>

Ich bin sehr neugierig auf die + Operator bei der Verwendung mit Arrays in JavaScript. Dies entspricht dem Ergebnis des Videos.

Empty Array + Object
[] + {}
result:
[Object]

Dies entspricht dem Ergebnis des Videos. Was ist hier los? Warum ist dies ein Objekt. Was bewirkt die + Betreiber tun?

Object + Empty Array
{} + []
result:
[Object]

Das stimmt nicht mit dem Video überein. Das Video suggeriert, dass das Ergebnis 0 ist, während ich [Objekt] erhalte.

Object + Object
{} + {}
result:
[Object][Object]

Das passt auch nicht zum Video, und wie führt die Ausgabe einer Variablen zu zwei Objekten? Vielleicht ist mein JSFiddle falsch.

Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN

Mit wat + 1 erhält man wat1wat1wat1wat1 ...

Ich vermute, dies ist nur einfaches Verhalten, dass der Versuch, eine Zahl von einer Zeichenfolge zu subtrahieren Ergebnisse in NaN.

1511voto

Ventero Punkte 10875

Hier ist eine Liste mit Erklärungen für die Ergebnisse, die Sie sehen (und die Sie sehen sollten). Die Referenzen, die ich verwende, stammen aus der ECMA-262-Norm .

  1. [] + []

    Bei Verwendung des Additionsoperators werden sowohl der linke als auch der rechte Operand zunächst in Primitive umgewandelt ( §11.6.1 ). Gemäß §9.1 Bei der Konvertierung eines Objekts (in diesem Fall ein Array) in ein Primitivum wird dessen Standardwert zurückgegeben, der für Objekte mit einem gültigen toString() Methode ist das Ergebnis des Aufrufs object.toString() ( §8.12.8 ). Für Arrays ist dies dasselbe wie der Aufruf von array.join() ( §15.4.4.2 ). Die Verknüpfung eines leeren Arrays ergibt eine leere Zeichenkette, so dass Schritt #7 des Additionsoperators die Verkettung zweier leerer Zeichenketten zurückgibt, also die leere Zeichenkette.

  2. [] + {}

    Ähnlich wie [] + [] werden beide Operanden zunächst in Primitive umgewandelt. Bei "Objektobjekten" (§15.2) ist dies wiederum das Ergebnis des Aufrufs object.toString() die für nicht-nullwertige, nicht-undefinierte Objekte lautet "[object Object]" ( §15.2.4.2 ).

  3. {} + []

    En {} wird hier nicht als Objekt geparst, sondern als leerer Block ( §12.1 (zumindest solange Sie nicht erzwingen, dass diese Anweisung ein Ausdruck ist, aber dazu später mehr). Der Rückgabewert von leeren Blöcken ist leer, also ist das Ergebnis dieser Anweisung das gleiche wie +[] . Der unäre + Betreiber ( §11.4.6 ) gibt zurück ToNumber(ToPrimitive(operand)) . Wie wir bereits wissen, ToPrimitive([]) ist die leere Zeichenkette, und nach §9.3.1 , ToNumber("") ist 0.

  4. {} + {}

    Ähnlich wie im vorigen Fall ist die erste {} wird als Block mit leerem Rückgabewert geparst. Nochmals, +{} ist dasselbe wie ToNumber(ToPrimitive({})) y ToPrimitive({}) es "[object Object]" (siehe [] + {} ). Um also das Ergebnis von +{} müssen wir anwenden ToNumber auf der Zeichenkette "[object Object]" . Wenn Sie die Schritte von §9.3.1 erhalten wir NaN als Ergebnis:

    Wenn die Grammatik den String nicht als eine Erweiterung von StringNumericLiteral dann ist das Ergebnis von ToNumber es NaN .

  5. Array(16).join("wat" - 1)

    Gemäß §15.4.1.1 y §15.4.2.2 , Array(16) erstellt ein neues Array mit der Länge 16. Um den Wert des zu verbindenden Arguments zu erhalten, §11.6.2 Die Schritte #5 und #6 zeigen, dass wir beide Operanden in eine Zahl umwandeln müssen, indem wir ToNumber . ToNumber(1) ist einfach 1 ( §9.3 ), während ToNumber("wat") ist wieder NaN gemäß §9.3.1 . Nach Schritt 7 von §11.6.2 , §11.6.3 schreibt vor, dass

    Wenn einer der beiden Operanden NaN ist das Ergebnis NaN .

    Das Argument gegen Array(16).join es NaN . Nach §15.4.4.5 ( Array.prototype.join ), müssen wir aufrufen ToString auf das Argument, das lautet "NaN" ( §9.8.1 ):

    Si m es NaN die Zeichenkette zurück. "NaN" .

    Nach Schritt 10 von §15.4.4.5 erhalten wir 15 Wiederholungen der Verkettung von "NaN" und die leere Zeichenkette, was dem Ergebnis entspricht, das Sie sehen. Bei der Verwendung von "wat" + 1 anstelle von "wat" - 1 als Argument, wandelt der Additionsoperator 1 in eine Zeichenkette umzuwandeln, anstatt "wat" zu einer Zahl, so dass es effektiv aufruft Array(16).join("wat1") .

Die Frage, warum Sie unterschiedliche Ergebnisse für die {} + [] Fall: Wenn Sie es als Funktionsargument verwenden, erzwingen Sie, dass die Anweisung ein ExpressionStatement was es unmöglich macht, die Daten zu analysieren. {} als leeren Block, so dass er stattdessen als leeres Objektliteral geparst wird.

33voto

CR Drost Punkte 9299

Dies ist mehr ein Kommentar als eine Antwort, aber aus irgendeinem Grund kann ich nicht auf Ihre Frage eingehen. Ich wollte Ihren JSFiddle-Code korrigieren. Allerdings habe ich dies auf Hacker News gepostet und jemand schlug vor, dass ich es hier neu posten.

Das Problem im JSFiddle-Code ist, dass ({}) (öffnende geschweifte Klammern innerhalb von Klammern) ist nicht dasselbe wie {} (öffnende geschweifte Klammern als Beginn einer Code-Zeile). Wenn Sie also eingeben out({} + []) zwingen Sie die {} etwas zu sein, was es nicht ist, wenn Sie eingeben {} + [] . Dies ist Teil der allgemeinen 'wat'-ness von Javascript.

Der Grundgedanke war einfach: JavaScript sollte beide Formen ermöglichen:

if (u)
    v;

if (x) {
    y;
    z;
}

Zu diesem Zweck wurden zwei Interpretationen der ersten Klammer vorgenommen: 1. sie ist nicht erforderlich und 2. kann es erscheinen überall .

Das war ein falscher Schritt. Echter Code hat keine öffnende Klammer, die mitten im Nirgendwo erscheint, und echter Code neigt auch dazu, anfälliger zu sein, wenn er die erste Form statt der zweiten verwendet. (Bei meinem letzten Job wurde ich etwa jeden zweiten Monat zu einem Kollegen gerufen, weil seine Änderungen an meinem Code nicht funktionierten und das Problem darin bestand, dass er eine Zeile zum "if" hinzugefügt hatte, ohne geschweifte Klammern hinzuzufügen. Ich habe mir schließlich angewöhnt, dass geschweifte Klammern immer erforderlich sind, auch wenn man nur eine Zeile schreibt).

Glücklicherweise wird eval() in vielen Fällen die volle Funktionsfähigkeit von JavaScript wiedergeben. Der JSFiddle-Code sollte lauten:

function out(code) {
    function format(x) {
        return typeof x === "string" ?
            JSON.stringify(x) : x;
    }   
    document.writeln('&gt;&gt;&gt; ' + code);
    document.writeln(format(eval(code)));
}
document.writeln("<pre>");
out('[] + []');
out('[] + {}');
out('{} + []');
out('{} + {}');
out('Array(16).join("wat" + 1)');
out('Array(16).join("wat - 1")');
out('Array(16).join("wat" - 1) + " Batman!"');
document.writeln("</pre>");

[Außerdem ist das 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() beinhaltet.]

20voto

Axel Rauschmayer Punkte 24061

Ich unterstütze die Lösung von @Ventero. Wenn Sie möchten, können Sie mehr ins Detail gehen, wie + wandelt seine Operanden um.

Erster Schritt (§ 9.1): beide Operanden in Primitive umwandeln (primitive Werte sind undefined , null , Boolesche Werte, Zahlen, Zeichenketten; alle anderen Werte sind Objekte, einschließlich Arrays und Funktionen). Wenn ein Operand bereits primitiv ist, sind Sie fertig. Wenn nicht, ist er ein Objekt. obj und die folgenden Schritte werden durchgeführt:

  1. Rufen Sie an. obj.valueOf() . Wenn er ein Primitiv zurückgibt, sind Sie fertig. Direkte Instanzen von Object und Arrays geben sich selbst zurück, Sie sind also noch nicht fertig.
  2. Rufen Sie an. obj.toString() . Wenn er ein Primitiv zurückgibt, sind Sie fertig. {} y [] geben beide eine Zeichenkette zurück, so dass Sie fertig sind.
  3. Andernfalls wird ein TypeError .

Bei Daten werden Schritt 1 und 2 vertauscht. Sie können das Konvertierungsverhalten wie folgt beobachten:

var obj = {
    valueOf: function () {
        console.log("valueOf");
        return {}; // not a primitive
    },
    toString: function () {
        console.log("toString");
        return {}; // not a primitive
    }
}

Interaktion ( Number() zuerst in Primitiv und dann in Zahl konvertiert):

> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value

Zweiter Schritt (§ 11.6.1): Wenn einer der Operanden eine Zeichenkette ist, wird der andere Operand ebenfalls in eine Zeichenkette umgewandelt, und das Ergebnis wird durch Verkettung zweier Zeichenketten erzeugt. Andernfalls werden beide Operanden in Zahlen umgewandelt, und das Ergebnis wird durch ihre Addition erzielt.

Ausführlichere Erklärung des Umwandlungsprozesses: " Was bedeutet {} + {} in JavaScript? "

14voto

Mariusz Nowak Punkte 30152

Wir können uns auf die Spezifikation beziehen, und das ist großartig und sehr genau, aber die meisten Fälle können auch mit den folgenden Aussagen verständlicher erklärt werden:

  • + y - Operatoren arbeiten nur mit primitiven Werten. Genauer gesagt + (Addition) arbeitet entweder mit Zeichenketten oder Zahlen, und + (unär) und - (Subtraktion und unär) funktioniert nur mit Zahlen.
  • Alle nativen Funktionen oder Operatoren, die einen primitiven Wert als Argument erwarten, wandeln dieses Argument zunächst in den gewünschten primitiven Typ um. Dies geschieht mit valueOf o toString die auf jedem Objekt verfügbar sind. Das ist der Grund, warum solche Funktionen oder Operatoren keine Fehler auslösen, wenn sie auf Objekten aufgerufen werden.

Das können wir also sagen:

  • [] + [] ist identisch mit String([]) + String([]) was dasselbe ist wie '' + '' . Ich habe bereits erwähnt, dass + (Addition) ist auch für Zahlen gültig, aber es gibt keine gültige Zahlendarstellung eines Arrays in JavaScript, daher wird stattdessen die Addition von Zeichenketten verwendet.
  • [] + {} ist identisch mit String([]) + String({}) was dasselbe ist wie '' + '[object Object]'
  • {} + [] . Diese Frage verdient eine nähere Erläuterung (siehe Antwort von Ventero). In diesem Fall werden geschweifte Klammern nicht als Objekt, sondern als leerer Block behandelt, so dass es sich als dasselbe herausstellt wie +[] . Unär + funktioniert nur mit Zahlen, also versucht die Implementierung, eine Zahl aus [] . Zunächst versucht sie valueOf was im Fall von Arrays dasselbe Objekt zurückgibt, also wird der letzte Ausweg versucht: die Umwandlung eines toString Ergebnis zu einer Zahl. Wir können es schreiben als +Number(String([])) was dasselbe ist wie +Number('') was dasselbe ist wie +0 .
  • Array(16).join("wat" - 1) Subtraktion - funktioniert nur mit Zahlen, ist also dasselbe wie: Array(16).join(Number("wat") - 1) als "wat" kann nicht in eine gültige Zahl umgewandelt werden. Wir erhalten NaN und jede arithmetische Operation auf NaN Ergebnisse mit NaN also haben wir: Array(16).join(NaN) .

1voto

Zur Untermauerung dessen, was bereits gesagt wurde.

Die Ursache für dieses Verhalten ist teilweise auf die schwache Typisierung von JavaScript zurückzuführen. Zum Beispiel ist der Ausdruck 1 + "2" mehrdeutig, da es zwei mögliche Interpretationen auf der Grundlage der Operandentypen (int, string) und (int int) gibt:

  • Der Benutzer möchte zwei Zeichenketten miteinander verknüpfen, Ergebnis: "12"
  • Benutzer will zwei Zahlen addieren, Ergebnis: 3

Bei unterschiedlichen Eingabearten erhöhen sich also die Ausgabemöglichkeiten.

Der Additionsalgorithmus

  1. Operanden in primitive Werte umwandeln

Die JavaScript-Primitive sind string, number, null, undefined und boolean (Symbol kommt bald in ES6). Jeder andere Wert ist ein Objekt (z. B. Arrays, Funktionen und Objekte). Der Zwangsprozess zur Umwandlung von Objekten in primitive Werte wird folgendermaßen beschrieben:

  • Wenn beim Aufruf von object.valueOf() ein primitiver Wert zurückgegeben wird, dann wird dieser Wert zurückgegeben, andernfalls wird fortgefahren

  • Wenn beim Aufruf 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 ist die Reihenfolge, dass toString vor valueOf aufgerufen wird.

  1. Wenn ein Operandenwert eine Zeichenkette ist, wird eine Zeichenkettenverkettung durchgeführt

  2. Andernfalls wandeln Sie beide Operanden in ihren numerischen Wert um und addieren diese Werte

Die Kenntnis der verschiedenen Zwangswerte von Typen in JavaScript hilft, die verwirrenden Ausgaben zu verdeutlichen. Siehe die nachstehende Tabelle der Zwangswerte

+-----------------+-------------------+---------------+
| Primitive Value |   String value    | Numeric value |
+-----------------+-------------------+---------------+
| null            | “null”            | 0             |
| undefined       | “undefined”       | NaN           |
| true            | “true”            | 1             |
| false           | “false”           | 0             |
| 123             | “123”             | 123           |
| []              | “”                | 0             |
| {}              | “[object Object]” | NaN           |
+-----------------+-------------------+---------------+

Es ist auch gut zu wissen, dass JavaScript's +-Operator links-assoziativ ist, da dies bestimmt, was die Ausgabe wird Fälle mit mehr als einem +-Operation sein.

Die Nutzung der 1 + "2" ergibt also "12", weil jede Addition mit einer Zeichenkette immer auf die Verkettung von Zeichenketten hinausläuft.

Weitere Beispiele finden Sie unter dieser Blogbeitrag (Haftungsausschluss: Ich habe es geschrieben).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X