2010-03-21 2 views
100

En Javascript, chaque objet a une méthode valueOf() et toString(). J'aurais pensé que la méthode toString() est invoquée chaque fois qu'une conversion de chaîne est appelée, mais apparemment elle est tronquée par valueOf().valueOf() vs toString() en Javascript

Par exemple, le code

var x = {toString: function() {return "foo"; }, 
     valueOf: function() {return 42; }}; 
window.console.log ("x="+x); 
window.console.log ("x="+x.toString()); 

imprimera

x=42 
x=foo 

Cela me semble en arrière .. si x était un nombre complexe, par exemple, je veux valueOf() pour donner moi sa grandeur, mais chaque fois que je voulais convertir en une chaîne je voudrais quelque chose comme "a + bi". Et je ne voudrais pas avoir à appeler toString() explicitement dans les contextes qui impliquent une chaîne.

Est-ce juste comme ça?

+5

Avez-vous essayé 'window.console.log (x);' ou 'alert (x);'? – Li0liQ

+4

Ils donnent respectivement "Object" et "foo". Truc amusant. – brainjam

+0

En fait, alerte (x); donne "foo", et window.console.log (x); donne "foo {}" dans Firebug et l'ensemble de l'objet dans la console Chrome. – brainjam

Répondre

95

La raison pour laquelle ("x =" + x) donne "x = valeur" et non "x = tostring" " est le suivant. Lors de l'évaluation de "+", javascript collecte d'abord les valeurs primitives des opérandes, puis décide si l'ajout ou la concaténation doit être appliqué, en fonction du type de chaque primitive.

Alors, voici comment vous pensez qu'il fonctionne

a + b: 
    pa = ToPrimitive(a) 
    if(pa is string) 
     return concat(pa, ToString(b)) 
    else 
     return add(pa, ToNumber(b)) 

et c'est ce qui se passe réellement

a + b: 
    pa = ToPrimitive(a) 
    pb = ToPrimitive(b)* 
    if(pa is string || pb is string) 
     return concat(ToString(pa), ToString(pb)) 
    else 
     return add(ToNumber(pa), ToNumber(pb)) 

C'est, toString est appliquée au résultat de valueOf, pas à votre objet original .

Pour plus d'informations, consultez la section 11.6.1 The Addition operator (+) dans la spécification de langage ECMAScript.


* Appelé dans un contexte de chaîne, ToPrimitive -t Invoke toString, mais ce n'est pas le cas ici, parce que « + » n'impose pas de contexte de type.

+2

Le conditionnel dans le bloc "réellement" ne devrait-il pas lire "si (pa est une chaîne && pb est une chaîne)"? I.e "&&" au lieu de "||" ? – brainjam

+3

La norme dit définitivement "ou" (voir le lien). – user187291

+0

Oui, vous avez raison. – brainjam

64

est ici un peu plus en détail, avant de passer à la réponse:

var x = { 
    toString: function() { return "foo"; }, 
    valueOf: function() { return 42; } 
}; 

alert(x); // foo 
"x=" + x; // "x=42" 
x + "=x"; // "42=x" 
x + "1"; // 421 
x + 1; // 43 
["x=", x].join(""); // "x=foo" 

La fonction est toStringpas « forgées de toutes pièces » par valueOf en général. La norme ECMAScript répond assez bien à cette question. Chaque objet a une propriété [[DefaultValue]], qui est calculée à la demande. Lorsque vous demandez cette propriété, l'interpréteur fournit également un "indice" pour le type de valeur qu'il attend. Si l'indice est String, alors toString est utilisé avant valueOf. Mais, si l'indice est Number, alors valueOf sera utilisé en premier. Notez que si une seule est présente, ou qu'elle renvoie une primitive, elle appellera généralement l'autre comme deuxième choix.

L'opérateur + fournit toujours l'indicateur Number, même si le premier opérande est une valeur de chaîne. Même s'il demande x pour sa représentation Number, puisque le premier opérande renvoie une chaîne de [[DefaultValue]], il effectue une concaténation de chaîne.

Si vous voulez garantir que toString est appelée pour la concaténation de chaîne, utilisez un tableau et la méthode .join("").

(ActionScript 3.0 modifie légèrement le comportement de +, cependant. Si l'un des opérandes est un String, elle sera traitée comme un opérateur de concaténation de chaîne et utilisez la touche String quand il appelle [[DefaultValue]]. Ainsi, AS3, cet exemple donne "foo, x = foo, foo = x, foo1, 43, x = foo".)

+0

Notez également que si 'valueOf' ou' toString' renvoient des primitives non-primitives, elles sont ignorées. Si aucun n'existe, ou aucun ne renvoie une primitive, alors un 'TypeError' est levé. – bcherry

+1

Merci bcherry, c'est le calibre de la réponse que j'espérais. Mais ne devrait pas x + "x ="; rendement "42x ="? Et x + "1"; Rend 421? De plus, avez-vous une URL pour la partie pertinente de la norme ECMAScript? – brainjam

+0

Oups, oui. Une faute de frappe et une erreur de tester '" 1 "+ 1', respectivement. J'ai modifié le message original. Merci :) Voici la spécification ECMAScript 3 (pdf): http://www.mozilla.org/js/language/E262-3.pdf Vous êtes à la recherche de '8.6.2.6 [[DefaultValue]] (indice) ', qui se trouve à la page 35. – bcherry