ValueOf() против toString() в Javascript

В Javascript каждый объект имеет метод valueOf() и toString(). Я бы подумал, что метод toString() вызывается всякий раз, когда вызывается преобразование строки, но, по-видимому, оно сфабриковано значениемOf().

Например, код

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

напечатает

x=42
x=foo

Это поражает меня как назад. Если x было сложным числом, например, я бы хотел, чтобы valueOf() дал мне свою величину, но всякий раз, когда я хотел преобразовать в строку, я хотел бы получить что-то вроде "a + bi". И я бы не хотел, чтобы я вызывал toString() явно в контекстах, которые подразумевали строку.

Это так, как есть?

Ответ 1

Причина, по которой ( "x =" + x) дает "x = значение", а не "x = tostring", следующая. При оценке "+" javascript сначала собирает примитивные значения операндов, а затем решает, следует ли применять добавление или конкатенацию в зависимости от типа каждого примитива.

Итак, так вы думаете, что это работает

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

и это то, что на самом деле происходит

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

То есть toString применяется к результату valueOf, а не к исходному объекту.

Подробнее см. раздел 11.6.1 Оператор добавления (+) в спецификации языка ECMAScript.


* При вызове в контексте строки ToPrimitive вызывает toString, но здесь это не так, потому что '+' не применяет какой-либо контекст типа.

Ответ 2

Вот немного более подробно, прежде чем я получу ответ:

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"

Функция toString в общем случае не "сбита" с помощью valueOf. Стандарт ECMAScript на самом деле очень хорошо отвечает на этот вопрос. Каждый объект имеет свойство [[DefaultValue]], которое вычисляется по требованию. При запросе этого свойства интерпретатор также предоставляет "подсказку" о том, какую ценность он ожидает. Если подсказка String, тогда toString используется до valueOf. Но если подсказка Number, тогда будет использоваться valueOf. Обратите внимание, что если присутствует только один или он возвращает не-примитив, он обычно вызывает второй вариант.

Оператор + всегда предоставляет подсказку Number, даже если первый операнд является строковым значением. Несмотря на то, что он запрашивает x для своего представления Number, так как первый операнд возвращает строку из [[DefaultValue]], он выполняет конкатенацию строк.

Если вы хотите гарантировать, что toString вызывается для конкатенации строк, используйте массив и метод .join("").

(ActionScript 3.0 немного изменяет поведение +. Если любой из операндов является String, он будет рассматривать его как оператор конкатенации строк и использовать подсказку String, когда он вызывает [[DefaultValue]]., в AS3 этот пример дает "foo, x = foo, foo = x, foo1, 43, x = foo".)