参考:https://github.com/mqyqingfeng/Blog/issues/159
类型转换
先总结
看 valueOf
和 toString
的调用顺序和返回值,如果返回值为原始值则使用,如果两个函数都没返回原始值,则报错。
valueOf
和 toString
方法的调用顺序在这两个方法都返回原始值的时候有用,因为先被调用的那个方法的返回值会作为运算的值。
// 每个对象都有 toString 和 valueOf 方法,其中
// toString 尝试返回对象的字符串表示
// valueOf 尝试返回对象的数字表示
// 这两个方法都有可能返回原始类型的值,但也有可能返回对象本身(非原始类型)
// 要执行运算时,会根据运算规则调用 toString 或者 valueOf 方法,但是需要注意的是调用的方法会返回原始类型的值
// 例子
// hook obj toString and valueOf
// 因为放到浏览器环境里测试不加 test 属性会导致非测试代码的 toString 被调用输出
// 所以要加个 test 属性以区分代码来源
const ots = Object.prototype.toString
Object.prototype.toString = function () {
if (this.test) {
console.log('To String called')
}
return ots.call(this)
}
const vo = Object.prototype.valueOf
Object.prototype.valueOf = function () {
if (this.test) {
console.log('ValueOf String called')
}
return vo.call(this)
}
// 运行结果:
// Number({ test: 1 })
// ValueOf String called
// To String called
// NaN
// 可以看到 Number 方法先调用了 valueOf,但是 valueOf 没有返回原始值,故调用了 toString
// 得到结果 NaN
Number({})
// 运行结果
// ValueOf String called
// 0
// 同样先调用 valueOf,发现返回值为 0,所以直接用 0 作为输入
const a = []
undefined
a.test = 1
Number(a)
[] + {}、({}) + [] 等奇怪的加法运算
a = {
valueOf() {
return 10
},
toString() {
return 0
}
}
b = []
b.valueOf = function() {
return 50
}
b.toString = function() {
return 100
}
// Number 先调用 valueOf,故返回值为 10
Number(a)
// 同理,先调用 toString,返回值为 '0'
String(a)
// 同理
Number(b)
String(b)
// 20
a + a
// 100
b + b
// 110
a + b + b
// 120
a + 100
// '10a'
a + 'a'
加号的运算规则
摘自:https://github.com/mqyqingfeng/Blog/issues/159
- 将 + 号两边的变量转为原型类型,对于对象类型的变量,会先调用 valueOf 方法,如果是原始类型就返回;
- 如果不是原始类型就调用 toString 方法。也就是上面的 ToPrimitive 方法
- 如果+号两侧有一个 string 类型,将另一个也转为 string 类型进行字符串拼接
- 否则按照 number 类型计算(转成元素类型后为 number ,且另一个运算数也是 number)
回头看上面的代码,因为 a + a
是两个对象相加,所以会先调用 valueOf
方法,同理 a + 10
因为 a 被转为 number,所以整体也是 number 运算。
而 a + '0'
则是因为有 string 参与运算,将 a.valueOf
的结果当成 string 看待,得到 ‘100’
加号运算规则重点再总结
上面摘自别人的总结,下面用自己的语言总结下
如果加号遇到对象类型,会先调用该对象的 valueOf 看结果是不是原始类型,如果不是继续调用 toString 方法,都不是原始类型则报错,得到原始类型后看另一个运算数是什么类型,为 string 则整体 string(当然如果自己得到的元素类型为 string,整体也是 string),否则进行 number 运算。
toString 和 valueOf 返回的都不是原始类型运算结果示例:
o = {
valueOf(){
return {}
},
toString(){
return {}
}
}
// 报错
// VM703:1 Uncaught TypeError: Cannot convert object to primitive value
o + 10
可以看出 toString 和 valueOf 的值是 JS 进行运算最终落实到的值,这样就能解释这章标题执行运算时得到的一些奇怪结果的原因了。