777 votes

Quelle est l’explication pour ces étranges comportements JavaScript mentionné dans le ' Wat ' parler pour CodeMash 2012 ?

Le "Wat" parler pour CodeMash 2012 , fondamentalement, des points un peu bizarre bizarreries avec Ruby et JavaScript.

J'ai fait un JSFiddle des résultats à http://jsfiddle.net/fe479/9/.

Les comportements spécifiques à JavaScript (que je ne connais pas Ruby) sont énumérés ci-dessous.

J'ai trouvé dans le JSFiddle que certains de mes résultats ne correspondent pas à ceux de la vidéo, et je ne suis pas sûr pourquoi. Cependant, je suis curieux de savoir comment JavaScript est de la manipulation à l'œuvre derrière les scènes dans chaque cas.

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

Je suis assez curieux de connaître le + de l'opérateur lorsqu'il est utilisé avec les tableaux en JavaScript. Cela correspond à la vidéo.

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

Cela correspond à la vidéo. Ce qui se passe ici? Pourquoi est-ce un objet. Ce qui ne l' + opérateur?

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

Cela ne correspond pas à la vidéo. La vidéo suggère que le résultat est égal à 0, alors que je reçois [Objet].

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

Cela ne correspond pas à la vidéo, et comment la sortie d'une variable de résultat dans les deux objets? Peut-être que mon JSFiddle est faux.

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

Faire wat + 1 wat1wat1wat1wat1...

Je suppose que c'est juste simple comportement que de tenter de soustraire un nombre à partir d'une chaîne de résultats NaN.

1510voto

Ventero Points 6800

Voici une liste d'explications pour les résultats que vous voyez (et supposé voir). Les références que j'utilise sont de la norme ECMA-262.

  1. [] + []

    Lors de l'utilisation de l'opérateur d'addition, à la fois la gauche et la droite opérandes sont convertis à des primitives d'abord (§11.6.1). Selon §9.1, la conversion d'un objet (dans ce cas, un tableau) à une primitive renvoie sa valeur par défaut, qui pour les objets avec une pièce d' toString() méthode est le résultat de l'appel d' object.toString() (§8.12.8). Pour les tableaux c'est le même que l'appel à la array.join() (§15.4.4.2). Joindre un tableau vide résultats dans une chaîne vide, alors l'étape n ° 7 de l'opérateur d'addition renvoie la concaténation de deux chaînes vides, qui est la chaîne vide.

  2. [] + {}

    Semblable à l' [] + [], les deux opérandes sont convertis à des primitives de première. Pour Objet "objets" (§15.2), c'est à nouveau le résultat de l'appel d' object.toString(), pour les non-nulle, non-non défini objets est - "[object Object]" (§15.2.4.2).

  3. {} + []

    L' {} ici n'est pas analysée comme un objet, mais plutôt comme un bloc vide (§12.1, au moins tant que vous n'êtes pas forcer pour être une expression, mais plus sur cela plus tard). La valeur de retour de blocs vides est vide, donc le résultat de cette instruction est la même que +[]. Unaire + opérateur (§11.4.6) renvoie ToNumber(ToPrimitive(operand)). Comme nous le savons déjà, ToPrimitive([]) est la chaîne vide, et selon §9.3.1, ToNumber("") est de 0.

  4. {} + {}

    Similaire au cas précédent, la première {} est analysée comme un bloc avec retour à vide de la valeur. Encore une fois, +{} est le même que ToNumber(ToPrimitive({})), et ToPrimitive({}) est "[object Object]" (voir [] + {}). Donc, pour obtenir le résultat de l' +{}, nous devons appliquer ToNumber sur la corde "[object Object]". En suivant les étapes du §9.3.1, nous obtenons NaN suite:

    Si la grammaire ne peut pas interpréter la Chaîne comme une expansion de StringNumericLiteral, alors le résultat de ToNumber est NaN.

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

    Selon §15.4.1.1 et §15.4.2.2, Array(16) crée un nouveau tableau avec une longueur de 16. Pour obtenir la valeur de l'argument de rejoindre, §11.6.2 étapes #5 et #6 montrent que nous devons convertir les deux opérandes d'un numéro à l'aide d' ToNumber. ToNumber(1) est tout simplement 1 (§9.3), alors que l' ToNumber("wat") nouveau est - NaN selon §9.3.1. Suite à l'étape 7 du §11.6.2, §11.6.3 dicte que

    Si l'un des deux opérandes est NaN, le résultat est NaN.

    Donc, l'argument d' Array(16).join est NaN. Suivant §15.4.4.5 (Array.prototype.join), nous devons faire appel ToString sur l'argument, qui est - "NaN" (§9.8.1):

    Si m est NaN, le retour de la Chaîne d' "NaN".

    Suite à l'étape 10 §15.4.4.5, nous obtenons 15 répétitions de la concaténation de "NaN" et la chaîne vide, ce qui est égal au résultat que vous voyez. Lors de l'utilisation d' "wat" + 1 au lieu de "wat" - 1 comme argument, l'opérateur d'addition convertit 1 d'une chaîne de caractères au lieu de convertir "wat" d'un nombre, il appelle efficacement Array(16).join("wat1").

Pourquoi vous voyez des résultats différents pour l' {} + [] cas: Lorsque l'utiliser comme un argument de fonction, vous êtes en forçant l'état à un ExpressionStatement, ce qui rend impossible d'analyser {} comme bloc vide, donc c'est plutôt analysée comme un objet vide littérale.

32voto

Chris Drost Points 1209

C'est plus un commentaire qu'une réponse, mais pour une raison que je ne peux pas commenter sur votre question. Je voulais corriger vos JSFiddle code. Cependant, j'ai posté ceci sur Hacker News et quelqu'un a suggéré que je repost ici.

Le problème dans le JSFiddle code, c'est qu' ({}) (ouverture des accolades à l'intérieur de parenthèses) n'est pas la même que {} (ouverture des accolades comme le début d'une ligne de code). Ainsi, lorsque vous tapez out({} + []) vous forcez l' {} être quelque chose dont il n'est pas lorsque vous tapez {} + []. Cela fait partie de l'ensemble de la 'wat'-ness de Javascript.

L'idée de base était simple JavaScript voulu permettre à la fois de ces formes:

if (u)
    v;

if (x) {
    y;
    z;
}

Pour ce faire, deux interprétations ont été faites de l'accolade d'ouverture: 1. il n'est pas nécessaire et 2. il peut apparaître n'importe où.

C'était un faux mouvement. Code réel n'a pas une accolade ouvrante et se trouvent au milieu de nulle part, et le code réel, tend aussi à être plus fragile quand il utilise la première forme plutôt que la deuxième. (Environ une fois tous les deux mois lors de mon dernier emploi, j'aurais appelé à un collègue de bureau lors de leurs modifications à mon code n'ont pas de travail, et le problème c'est qu'ils ont ajouté une ligne à la "si", sans en ajoutant des accolades. Finalement, j'ai juste pris l'habitude que les accolades sont toujours nécessaires, même lorsque vous êtes seulement à écrire une seule ligne.)

Heureusement, dans de nombreux cas, la fonction eval() de répliquer le plein wat-ness de JavaScript. Le JSFiddle code devrait lire:

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

[Aussi c'est la première fois que j'ai écrit.writeln dans de nombreux de nombreux de nombreuses années, et je me sens un peu sale écrit quoi que ce soit impliquant à la fois le document.writeln() et la fonction eval().]

20voto

Axel Rauschmayer Points 2401

Je seconde @Ventero de la solution. Si vous le souhaitez, vous pouvez aller dans plus de détails quant à la façon dont + convertit ses opérandes.

Première étape (§9.1): convertir les deux opérandes de primitives (des valeurs primitives sont undefined, null, des booléens, des nombres, des chaînes; toutes les autres valeurs sont des objets, y compris les tableaux et les fonctions). Si un opérande est déjà primitif, vous avez terminé. Si non, c'est un objet, obj et les étapes suivantes sont effectuées:

  1. Appelez obj.valueOf(). Si elle renvoie un primitif, vous avez terminé. Direct des instances de Object et les tableaux de rentrer eux-mêmes, de sorte que vous ne sont pas encore faits.
  2. Appelez obj.toString(). Si elle renvoie un primitif, vous avez terminé. {} et [] à la fois renvoyer une chaîne, de sorte que vous êtes fait.
  3. Sinon, jetez un TypeError.

Pour les dates, l'étape 1 et 2 sont inversés. Vous pouvez observer le comportement de conversion comme suit:

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

L'Interaction (Number() premiers convertis à l'primitif ensuite pour le nombre):

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

Deuxième étape (§11.6.1): Si l'un des opérandes est une chaîne, l'autre opérande est également convertie en chaîne de caractères et le résultat est produit par la concaténation de deux chaînes. Sinon, les deux opérandes sont convertis en nombres, et le résultat est produite par l'ajout d'eux.

Des explications plus détaillées sur le processus de conversion: "qu'est-Ce que {} + {} en JavaScript?"

14voto

Mariusz Nowak Points 5879

Nous pouvons nous référer à la spécification et qui est grande et la plus précise, mais la plupart des cas, peut aussi être expliquée plus compréhensible façon avec les énoncés suivants:

  • + et - opérateurs de travailler uniquement avec des valeurs primitives. Plus précisément +(plus) fonctionne avec des chaînes ou des nombres, et +(unaire) et -(soustraction et unaire) fonctionne uniquement avec des nombres.
  • Toutes les fonctions natives ou des opérateurs qui attendent valeur primitive comme argument, va d'abord convertir l'argument souhaité de type primitif. Il est fait avec valueOf ou toString, qui sont disponibles sur n'importe quel objet. C'est la raison pour laquelle de telles fonctions ou opérateurs, ne jetez pas les erreurs lorsqu'il est appelé sur les objets.

Nous pouvons donc dire que:

  • [] + [] est le même que String([]) + String([]) qui est la même que '' + ''. Je l'ai mentionné ci-dessus que l' +(plus) est également valable pour les numéros, mais il n'y a pas de numéro valide de la représentation d'un tableau en JavaScript, donc plus de chaînes de caractères est utilisé à la place.
  • [] + {} est le même que String([]) + String({}) qui est la même que '' + '[object Object]'
  • {} + []. Cela mérite de plus amples explications (voir Ventero réponse). Dans ce cas, les accolades sont traités non pas comme un objet mais comme un bloc vide, de sorte qu'il s'avère être la même qu' +[]. Unaire + fonctionne uniquement avec les nombres, de sorte que la mise en œuvre tente d'obtenir un certain nombre de []. D'abord il essaie valueOf qui, dans le cas des tableaux renvoie le même objet, alors il essaie le dernier recours: la conversion d'un toString suite à un certain nombre. On peut l'écrire comme +Number(String([])) qui est la même que +Number('') qui est la même que +0.
  • Array(16).join("wat" - 1) soustraction - fonctionne uniquement avec les nombres, de sorte que c'est la même chose que: Array(16).join(Number("wat") - 1), "wat" ne peut pas être convertie en un nombre valide. Nous recevons NaN, et de toute opération arithmétique sur NaN résultats avec NaN, nous avons donc: Array(16).join(NaN).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X