深入剖析JavaScript中的对象与原始值之间的转换机制

时间:2022-01-26 16:32:41

我们都知道原始值之间是可以互相转换的,但是如果对象转原始值呢?

  • 所有的对象在布尔上下文(context)中均为 true 。所以对于对象,不存在 to-boolean 转换, 只有字符串和数值转换。
  • 数值转换发生在对象相减或应用数学函数时。例如, Date 对象(将在 日期和时间 一章中介 绍)可以相减, date1 - date2 的结果是两个日期之间的差值。
  • 至于字符串转换 —— 通常发生在我们像 alert(obj) 这样输出一个对象和类似的上下文中。

所以,由此可见对象转原始值的情况下大致可以分为数值转换和字符串转换两种,继续往下

什么是hint?

7.1.1.1 OrdinaryToPrimitive ( Ohint )

The abstract operation OrdinaryToPrimitive takes arguments O and hint. It performs the following steps when called:

  1. AssertType(O) is Object.
  2. Asserthint is either string or number.
  3. If hint is string, then
    1. Let methodNames be « "toString", "valueOf" ».
  4. Else,
    1. Let methodNames be « "valueOf", "toString" ».
  5. For each element name of methodNames, doThrow a TypeError exception.
    1. Let method be ? Get(Oname).
    2. If IsCallable(method) is true, then
      1. Let result be ? Call(methodO).
      2. If Type(result) is not Object, return result.

我们在ECMA规范中可以得知,hint是类型转换的变体,不太明白?没关系,继续往下

当在对象到字符串的转换中,hint的值便是"string"

var obj = {name:"谢绝先生"};
var anotherObj = {[obj]:"北有极光"};

当在对象到数字的转换中,hint的值便是"number"

var user = {name:"申屠肆"};
console.log(+user);

对于不确定转换的类型时,它将依据 hint的值为"default"  ,例如,二进制加法 + 可用于字符串(连接),也可以用于数字(相加),所以字符串和数字这两 种类型都可以

var sth = {name:"申屠肆"};
console.log(sth + 2);

接下来,更进一步,为了进行转换,JavaScript 尝试查找并调用三个对象方法:

  1. Symbol.toPrimitive
  2. toString
  3. valueOf

在进行类型转换的时候,首先会去查找个名为 Symbol.toPrimitive 的内建 symbol,像这样

obj[Symbol.toPrimitive] = function(hint) {
// 返回一个原始值
// hint = "string"、"number" 和 "default" 中的一个
}

该函数接收一个参数 hint,hint便是需要进行转换的原始值的类型;通过该函数我们可以完全掌控生成什么样的原始值,从而达到我们想要的目的,举个栗子:

var sth = {
name:"申屠肆",
money:1000,
[Symbol.toPrimitive](hint) {
if (hint == "string")
return this.name;
else
return this.money;
}
} console.log(+sth); // 1000
console.log(`${sth}`); // 申屠肆

古老的 toString / valueOf

如果没有 Symbol.toPrimitive ,那么 JavaScript 将尝试找到它们,并且按照下面的顺序进行 尝试: 对于 “string” hint, toString -> valueOf 。 其他情况, valueOf -> toString 。 这些方法必须返回一个原始值。如果 toString 或 valueOf 返回了一个对象,那么返回值会 被忽略(和这里没有方法的时候相同)。 默认情况下,普通对象具有 toString 和 valueOf 方法: toString 方法返回一个字符串 "[object Object]" 。 Symbol.toPrimitive obj[Symbol.toPrimitive] = function(hint) { // 返回一个原始值 // hint = "string"、"number" 和 "default" 中的一个 } let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // 转换演示: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 toString/valueOf ● valueOf 方法返回对象自身。

解释一下:

就是说如果hint 为 "string",在没有 Symbol.toPrimitive 的情况下,会优先查找 toString方法;

其他情况下("default","number"),在没有 Symbol.toPrimitive 的情况下,valueOf的优先级高一些;

至于为什么valueOf和toString方法返回的只能是原始值的情况下,valueOf还会返回对象本身,这是一个历史问题;

代码实例:

//Node.js环境下,根目录下 index.js文件

let user = {
name:'Joe',
money:1000,
toString() {
// "hint" 为 string
console.log('执行执行~',this.name);
return this.name;
},
valueOf() {
// hint 为 default 或者 number;
return this.money;
}
} module.exports = user;
//与index.js文件同目录下的测试文件

var assert = require('chai').assert;
var user = require('./index'); describe('对象转原始值',function() {
it('hint 为 string的情况下',function() {
assert( `${user}` === user.name,'成功的触发了toString方法');
});
it('hint 为 default的情况下',function() {
assert( user + 1 === user.money + 1,'成功的触发了valueOf方法');
});
it('hint 为 number的情况下',function() {
assert( +user === user.money,'成功的触发了valueOf方法');
});
})
// package.json
{
"name": "something",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"test":"mocha ./index.test.js"
},
"dependencies": {
"chai": "^4.3.0",
"mocha": "^8.2.1"
}
}

单元测试结果如下:

深入剖析JavaScript中的对象与原始值之间的转换机制

2021-02-04 23:56:54