【问题标题】:valueOf() vs. toString() in JavascriptJavascript 中的 valueOf() 与 toString()
【发布时间】:2010-03-21 02:25:36
【问题描述】:

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

就是这样吗?

【问题讨论】:

  • 你试过window.console.log (x);alert (x);吗?
  • 他们分别给出“Object”和“foo”。有趣的东西。
  • 其实alert(x);给出“foo”和 window.console.log (x);在 Firebug 中给出“foo {}”,在 Chrome 控制台中给出整个 Object。
  • 在 Firefox 33.0.2 alert(x) 显示 foowindow.console.log(x) 显示 Object { toString: x.toString(), valueOf: x.valueOf() }

标签: javascript


【解决方案1】:

("x="+x) 给出 "x=value" 而不是 "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,但这里不是这种情况,因为 '+' 不强制任何类型上下文。

【讨论】:

  • “实际”块中的条件不应该读作“if(pa is string && pb is string)”吗?即“&&”而不是“||” ?
  • 标准肯定说“或”(见链接)。
  • 是的,这完全正确——在串联中,字符串优先于其他类型。如果 either 操作数是字符串,则整个事物将连接为字符串。很好的答案。
【解决方案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,则在valueOf 之前使用toString。但是,如果提示是Number,那么将首先使用valueOf。请注意,如果只有一个存在,或者它返回一个非原始的,它通常会调用另一个作为第二选择。

+ 运算符始终提供提示 Number,即使第一个操作数是字符串值。尽管它向x 询问其Number 表示,但由于第一个操作数从[[DefaultValue]] 返回一个字符串,它会进行字符串连接。

如果要保证调用toString 进行字符串连接,请使用数组和.join("") 方法。

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

【讨论】:

  • 另请注意,如果valueOftoString 返回非基元,它们将被忽略。如果两者都不存在,或者都不返回原语,则抛出 TypeError
  • 谢谢 bcherry,这是我希望得到的答案。但不应该 x + "x=";产量“42x=”?和 x + "1";产量 421 ?另外,您是否有 ECMAScript 标准相关部分的 URL?
  • 实际上,'+' 不使用提示(参见 $11.6.1),因此 ToPrimitive 调用 [[DefaultValue]](no-hint),相当于 [[DefaultValue]](number)
  • 这似乎不是内置的 Date 类的情况。 ("" + new Date(0)) === new Date(0).toString()。将 Date 对象添加到某个对象时,它似乎总是返回其 toString() 值。
  • +1,谢谢!我找到了your blog-post in which you elaborate on this answer 并想在这里链接/分享它。这个答案真的很有帮助(包括 Dmitry A. Soshnikov 的评论)。
【解决方案3】:

TLDR

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

用于强制值的精确机制取决于被评估的表达式。在问题中,正在使用addition operator

加法运算符将首先确保两个操作数都是基元,在这种情况下,这涉及调用valueOf 方法。在此实例中未调用 toString 方法,因为对象 x 上覆盖的 valueOf 方法返回原始值。

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

最后,将结果字符串连接起来。

详情

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

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 的目的是返回对象的字符串表示形式。

例如:

console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'

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

在强制过程中,可以发生两种转换模式:

  1. 将对象转换为基元(可能涉及类型转换本身),以及
  2. 使用原始类型之一的构造函数对象直接转换为特定类型实例(即Number()Boolean()String() 等)

转换为基元

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

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

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

直接类型转换

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

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

所以:

 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 实例转换为可读字符串,而不是它们的数值。您可以使用Symbol.toPrimitive 在您自己的对象中覆盖@@toPrimitive

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

also

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-09
    • 1970-01-01
    • 2012-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多