Javascript中的valueOf()与toString()

时间:2010-03-21 02:25:37

标签: javascript

在Javascript中,每个对象都有一个valueOf()和toString()方法。我原以为每当调用字符串转换时都会调用toString()方法,但显然它被valueOf()所取代。

例如,代码

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

这就是这样吗?

3 个答案:

答案 0 :(得分:101)

(“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的结果,而不是原始对象。

有关进一步的参考,请参阅ECMAScript语言规范中的第11.6.1节The Addition operator ( + )


*在字符串上下文中调用时,ToPrimitive 调用toString,但这不是这种情况,因为'+'不强制执行任何类型的上下文。

答案 1 :(得分:72)

在我得到答案之前,这里有一些细节:

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的{​​{1}}表示,因为第一个操作数从Number返回一个字符串,它会进行字符串连接。

如果要保证为字符串连接调用[[DefaultValue]],请使用数组和toString方法。

(但是,ActionScript 3.0稍微修改了.join("")的行为。如果任一操作数是+,它会将其视为字符串连接运算符并使用提示{{1}当它调用String时。因此,在AS3中,这个例子产生“foo,x = foo,foo = x,foo1,43,x = foo”。)

答案 2 :(得分:2)

TLDR

类型强制转换(或隐式类型转换)启用弱类型输入,并在整个JavaScript中使用。大多数运算符(严格相等运算符===!==除外)和值检查运算符(例如if(value)...)将强制提供给它们的值,如果这些值不立即与操作兼容。

用于强制转换值的精确机制取决于所求值的表达式。在问题中,正在使用addition operator

加法运算符将首先确保两个操作数都是基元,在这种情况下,这涉及调用valueOf方法。在这种情况下,不会调用toString方法,因为在对象valueOf上重写的x方法返回一个原始值。

然后,因为问题中的一个操作数是一个字符串,所以两个操作数都转换为字符串。此过程使用抽象的内部操作ToString(注:大写),与对象(或其原型链)上的toString方法不同。

最后,将结果字符串串联在一起。

详细信息

在与JavaScript中每种语言类型(即Number,BigInt,String,Boolean,Symbol和Object)相对应的每个构造函数对象的原型上,有两种方法:valueOf和{{1} }。

toString的目的是检索与对象关联的原始值(如果有)。如果对象不具有基础原始值,则仅返回该对象。

如果针对基元调用valueOf,则该基元将以常规方式自动装箱,并返回基础基元值。请注意,对于字符串,基础原始值(即valueOf返回的值)是字符串表示形式本身。

以下代码显示valueOf方法从包装器对象返回基础原始值,并且它显示了不修改的对象实例与原始对象不对应,没有原始值要返回,因此它们仅返回自己。

valueOf

另一方面,console.log(typeof new Boolean(true)) // 'object' console.log(typeof new Boolean(true).valueOf()) // 'boolean' console.log(({}).valueOf()) // {} (no primitive value to return)的目的是返回对象的字符串表示形式。

例如:

toString

对于大多数操作,JavaScript会静默尝试将一个或多个操作数转换为所需的类型。选择此行为是为了使JavaScript易于使用。 JavaScript initially did not have exceptions,并且在此设计决策中也可能发挥了作用。这种隐式类型转换称为类型强制,它是JavaScript的松散(弱)类型系统的基础。这种行为背后的复杂规则旨在将类型转换的复杂性转移到语言本身中,并从您的代码中移出。

在强制过程中,可以有两种转换方式:

  1. 将对象转换为基元(可能涉及类型转换本身),并且
  2. 使用原始类型之一(例如console.log({}.toString()) // '[object Object]' console.log(new Number(1).toString()) // '1'Number()Boolean()等)的构造函数对象直接转换为特定类型实例

转换为原始

当尝试将非基本类型转换为要操作的基本类型时,将使用可选的“数字”或“字符串”的“提示”来调用抽象操作ToPrimitive。如果省略了提示,则默认提示为“数字”(除非String()方法已被覆盖)。如果提示是“字符串”,则首先尝试@@toPrimitive,然后尝试toString,如果valueOf没有返回原语。反之亦然。提示取决于请求转换的操作。

加法运算符不提供任何提示,因此首先尝试toString。减法运算符提供了“数字”的提示,因此首先尝试valueOf。我可以在规范中找到提示为“字符串”的唯一情况是:

  1. valueOf
  2. 抽象操作Object#toString,它将参数转换为可以用作属性键的值

直接类型转换

每个操作员都有自己的规则来完成操作。加法运算符将首先使用ToPropertyKey来确保每个操作数都是基元;然后,如果任何一个操作数都是一个字符串,它将故意在每个操作数上调用抽象操作ToPrimitive,以传递我们期望的字符串连接行为。在ToString步骤之后,如果两个操作数都不是字符串,则执行算术加法。

与加法不同,减法运算符不具有重载行为,因此将在首先使用ToPrimitive将其转换为基元的每个操作数上调用toNumeric

所以:

ToPrimitive

请注意, 1 + 1 // 2 '1' + 1 // '11' Both already primitives, RHS converted to string, '1' + '1', '11' 1 + [2] // '12' [2].valueOf() returns an object, so `toString` fallback is used, 1 + String([2]), '1' + '2', 12 1 + {} // '1[object Object]' {}.valueOf() is not a primitive, so toString fallback used, String(1) + String({}), '1' + '[object Object]', '1[object Object]' 2 - {} // NaN {}.valueOf() is not a primitive, so toString fallback used => 2 - Number('[object Object]'), NaN +'a' // NaN `ToPrimitive` passed 'number' hint), Number('a'), NaN +'' // 0 `ToPrimitive` passed 'number' hint), Number(''), 0 +'-1' // -1 `ToPrimitive` passed 'number' hint), Number('-1'), -1 +{} // NaN `ToPrimitive` passed 'number' hint', `valueOf` returns an object, so falls back to `toString`, Number('[Object object]'), NaN 1 + 'a' // '1a' Both are primitives, one is a string, String(1) + 'a' 1 + {} // '1[object Object]' One primitive, one object, `ToPrimitive` passed no hint, meaning conversion to string will occur, one of the operands is now a string, String(1) + String({}), `1[object Object]` [] + [] // '' Two objects, `ToPrimitive` passed no hint, String([]) + String([]), '' (empty string) 1 - 'a' // NaN Both are primitives, one is a string, `ToPrimitive` passed 'number' hint, 1-Number('a'), 1-NaN, NaN 1 - {} // NaN One primitive, one is an object, `ToPrimitive` passed 'number' hint, `valueOf` returns object, so falls back to `toString`, 1-Number([object Object]), 1-NaN, NaN [] - [] // 0 Two objects, `ToPrimitive` passed 'number' hint => `valueOf` returns array instance, so falls back to `toString`, Number('')-Number(''), 0-0, 0 内部对象是唯一的,因为它是覆盖默认Date方法的唯一内部对象,在该方法中,默认提示被假定为“字符串”(而不是“数')。这样做的原因是为了方便程序员,默认情况下将@@toPrimitive实例转换为可读字符串,而不是其数值。您可以使用Date在自己的对象中覆盖@@toPrimitive

以下网格显示了抽象相等运算符(Symbol.toPrimitive)(source)的强制结果:

enter image description here

请参见also