Javascript中的valueOf()与toString()

bra*_*jam 111 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());
Run Code Online (Sandbox Code Playgroud)

将打印

x=42
x=foo
Run Code Online (Sandbox Code Playgroud)

这让我觉得倒退..如果x是一个复数,例如,我希望valueOf()给我它的大小,但每当我想转换成一个字符串我就会想要像"a + bi"这样的东西.而且我不想在隐含字符串的上下文中显式调用toString().

这只是它的方式吗?

use*_*291 103

("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))
Run Code Online (Sandbox Code Playgroud)

这就是实际发生的事情

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))
Run Code Online (Sandbox Code Playgroud)

也就是说,toString应用于valueOf的结果,而不是原始对象.

有关进一步参考,请参阅ECMAScript语言规范中的第11.6.1节"加法运算符"(+).


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

  • 不应该在"实际"块中的条件读取"if(pa是string && pb是string)"吗?即"&&"而不是"||" ? (3认同)
  • 标准肯定说"或"(见链接). (3认同)
  • 是的,这是完全正确的——在连接中,字符串优先于其他类型。如果 *either* 操作数是字符串,则整个内容将连接为字符串。好答案。 (3认同)

bch*_*rry 73

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

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"
Run Code Online (Sandbox Code Playgroud)

一般而言,该toString功能并非 "胜过" valueOf.ECMAScript标准实际上很好地回答了这个问题.每个对象都有一个[[DefaultValue]]属性,可以按需计算.当要求这个属性时,解释器还提供了一个"提示",表示它期望的值.如果提示是String,则toString之前使用valueOf.但是,如果提示是Number,那么valueOf将首先使用.请注意,如果只有一个存在,或者它返回非基元,它通常会将另一个作为第二选择.

+运营商一直提供的提示Number,即使第一个操作数是一个字符串值.即使它要求x它的Number表示,因为第一个操作数从中返回一个字符串[[DefaultValue]],它会进行字符串连接.

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

(但是,ActionScript 3.0稍微修改了它的行为+.如果任一操作数是a String,它会将其视为字符串连接运算符并String在调用时使用提示[[DefaultValue]].因此,在AS3中,此示例产生"foo,x = foo,foo" = x,foo1,43,x = foo".)

  • 对于内置的Date类,情况似乎并非如此.`(""+ new Date(0))=== new Date(0).toString()`.当一个Date对象添加到某个东西时,它似乎总是返回它的`toString()`值. (9认同)
  • +1和Thx!我发现[你的博客文章中详细说明了这个答案](http://www.adequatelygood.com/Object-to-Primitive-Conversions-in-JavaScript.html)并想在这里链接/分享.这个答案真的很有帮助(包括Dmitry A. Soshnikov的评论). (6认同)
  • 实际上,'+'不使用提示(参见$ 11.6.1)因此ToPrimitive调用`[[DefaultValue]](no-hint)`,这相当于`[[DefaultValue]](number)`. (2认同)

Ben*_*Ben 6

总长DR

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

用于强制值的精确机制取决于正在评估的表达式。在该问题中,正在使用加法运算符。

加法运算符将首先确保两个操作数都是原语,在本例中,这涉及调用该valueOf方法。toString在此实例中未调用该方法,因为valueOf对象上的重写方法x返回原始值。

然后,因为问题中的操作数之一是字符串,所以两个操作数都转换为字符串。这个过程使用抽象的内部操作(注意:大写),并且与对象(或其原型链)上的方法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)
Run Code Online (Sandbox Code Playgroud)

toString另一方面,的目的是返回对象的字符串表示形式。

例如:

console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'
Run Code Online (Sandbox Code Playgroud)

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

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

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

转换为原语

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

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

  1. Object#toString
  2. 抽象操作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
Run Code Online (Sandbox Code Playgroud)

请注意,Date内在对象是唯一的,因为它是唯一覆盖默认@@toPrimitive方法的内在对象,其中默认提示被假定为“字符串”(而不是“数字”)。这样做的原因是为了Date方便程序员,默认情况下将实例转换为可读字符串,而不是它们的数值。您可以@@toPrimitive使用 覆盖您自己的对象Symbol.toPrimitive

下面的表格显示了抽象相等运算符 ( ==) 的强制转换结果(来源):

在此输入图像描述

附录

请注意,JavaScript 强制规则旨在维护行为的内部一致性,而不是为了满足您的直觉(尽管它们通常会满足您的直觉)。

因此存在违反直觉的边缘情况。例如,以下内容看起来违反直觉且不一致,但它是保持内部行为一致的产物

!![] // true because Boolean([]) => true, [[Negation]](true) => false => [[Negation]](false) => true
[] == false // true because [].valueOf() => [], which is not primitive, fallback to [].toString() => '', Boolean('') => false, false === false => true
Run Code Online (Sandbox Code Playgroud)

请参阅《你不懂 JS》中的和此处。