在标准 JavaScript ES6 环境中,.toString() 什么时候被调用过?

Within the standard JavaScript ES6 environment, when is .toString() ever called?

我才发现调用.toString()的时候是字符串拼接和字符串插值:

// Inside of Node:

> let arr = [1,3,5];

> arr.toString()
'1,3,5'

> "" + arr
'1,3,5'

> arr + ""
'1,3,5'

> `${arr}`
'1,3,5'

据推测,console.log打印出对象的字符串表示,应该使用toString(),但我不确定它是否toString()通常没有正确实现,所以console.log 做其他事情:

> console.log(arr);
[ 1, 3, 5 ]

> console.log("arr is %s", arr);
arr is [ 1, 3, 5 ]

那么在 JavaScript 本身内部,什么时候调用过 toString()

我认为通过多态性,我们自己编写的任何东西,我们都可以使用 ourObj.toString() 来获得我们对象的字符串表示形式作为字符串。但我想知道 JavaScript 本身(它的所有功能、库、类),什么时候真正调用 toString()

在 EcmaScript 语言规范的几个部分中,提到了 toString。一个重要的用途出现在抽象操作 OrdinaryToPrimitive 中:这个函数将寻找对象的 toStringvalueOf 方法,并执行它。优先级会受到 hint 参数的影响。

反过来,OrdinaryToPrimitive被抽象操作调用ToPrimitive

ToPrimitiveToNumberToStringToPropertyKey、关系比较、相等比较、表达式求值、Date 构造函数、几个字符串化方法,如 toJSON、...等

事实上,该语言充满了将要执行的内部操作 ToPrimitive。该规范有 200 多次引用 ToString.

例子

这里是一个带有toString方法实现的对象,目的是为了证明toString是内部调用的

然后跟随几个每个触发的表达式toString

// Preparation
let obj = {
    toString() {
        this.i = (this.i||0) + 1; // counter
        console.log("call #" + this.i);
        return "0";
    }
};

// Trigger toString via several constructs
({})[obj];
1 < obj; 
1 == obj;
1 + obj;
1 - obj;
+obj;
Math.abs(obj);
parseInt(obj);
new Date(obj);
new RegExp(obj);
new Number(obj);
Symbol(obj); 
"".localeCompare(obj);
[obj, null].sort();
[obj].join();
`${obj}`;
setTimeout(obj); // Not standard EcmaScript environment, but defined in the agent.

如果在 obj 上定义了 valueOf 方法,其中一些将不会触发 toString(),这将 return 一个原始值。

举例说明一些可能更常见的情况:

  1. 字符串插值(可能最有用)
  2. 字符串添加或连接
  3. 符号或字符串的创建
  4. 作为 属性 键
  5. Arrayjoin()
  6. 使用

节点控制台内部:

> let foo = { a: 123, toString: function() { return `an object with value ${this.a}`; } };

> foo
{ a: 123, toString: [Function: toString] }

> foo.toString()
'an object with value 123'

> foo.a = 456;
456

> foo.toString()
'an object with value 456'

> `${foo}`
'an object with value 456'

> "foo: " + foo
'foo: an object with value 456'

> "foo: ".concat(foo)
'foo: an object with value 456'

> let d = {};

> d[foo] = "hello";
'hello'

> d
{ 'an object with value 456': 'hello' }

> Symbol(foo)
Symbol(an object with value 456)

> String(foo)
'an object with value 456'

> let bar = Object.assign({}, foo);   // clone

> bar.a = 789;
789

> [foo, bar].join(" - ")
'an object with value 456 - an object with value 789'