js基础:valueOf、toString、[Symbol.toPrimitive] 方法解析

valueOf 和 toString,我们在日常使用中很少会手动调用,[Symbol.toPrimitive] 就更少了,这里记录一下他们的用途。

是什么

MDN 的说法是:

1
2
3
- valueOf() 方法返回指定对象的'原始值'。
- toString() 方法返回一个表示该对象的字符串。
- [Symbol.toPrimitive]() 当一个对象转换为对应的'原始值'时,会调用此函数。

问题来了, 什么是原始值 ? MDN 的说法是:

In JavaScript, a primitive (primitive value, primitive data type) is data that is not an object and has no methods. There are 7 primitive data types: string, number, bigint, boolean, undefined, symbol, and null.

基本上可以理解为,原始数据、原始值是一种既非对象也无方法的数据,也就是基本数据类型

不同类型对象的表现

用途

很少会显式地使用这三个方法(除了 toString 偶尔会用到)
当对象需要被转换成原始值的时候,就会被调用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
a: 1,
}
// 算术运算
obj + 1 // NaN
// 拼接
obj + ''
// 当成obj的key
const obj1 = {
[obj]: 1,
}
// 当做一些方法的参数
alert(obj)

以上这些场景发生的时候,就会隐式调用这三个方法。

优先级

重写三个方法,尝试每个转换场景,看看每个场景下,这三个函数的调用优先级;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 重写三个方法
const obj = {
valueOf() {
console.log('valueOf called')
return 1
},
toString() {
console.log('toString called')
return 2
},
[Symbol.toPrimitive]() {
console.log('Symbol.toPrimitive called')
return 3
},
}

// 比较
obj > 1 // Symbol.toPrimitive called
// 运算
obj + 1 // Symbol.toPrimitive called
obj + '' // Symbol.toPrimitive called
// 逻辑
!obj // 未调用
obj || true // 未调用
// 条件
if (obj) {
// 未调用
console.log('haha')
}
// 作为属性
const b = {
[obj]: 1, // Symbol.toPrimitive called
}
// 作为参数
alert(obj) // Symbol.toPrimitive called

结论一:Symbol.toPrimitive 调用优先级最高, 当存在 Symbol.toPrimitive 方法时, 所有转换都会调用它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 重写 valueOf 和 toString
const obj = {
valueOf() {
console.log('valueOf called')
return 1
},
toString() {
console.log('toString called')
return 2
},
}
// 比较
obj > 1 // valueOf called
// 运算
obj + 1 // valueOf called
obj + '' // valueOf called
obj * 1 // valueOf called
// 逻辑
!obj // 未调用
obj || true // 未调用
// 条件
if (obj) {
// 未调用
console.log('haha')
}
// 作为属性
const b = {
[obj]: 1, // toString called
}
// 作为参数
alert(obj) // toString called

结论二: 比较和算术运算 => valueOf, 作为对象的 key、作为函数参数时 => toString

无聊的运用

有一道面试题,是让 a == 1 && a == 2 && a == 3 成立;
老实说这种题目就是脑残,单纯是为了炫技,实际工作谁要这样写,不会被 n+1 ?

思路是,当使用 == 的时候会发生隐式转换,会调用 valueOf,所以只要劫持 valueOf 即可,解法:

1
2
3
4
5
6
7
8
9
10
class A {
constructor(value) {
this.value = value
}
valueOf() {
return this.value++
}
}
const a = new A(1)
console.log(a == 1 && a == 2 && a == 3)

如果是 === 怎么办,a === 1 && a === 2 && a === 3
=== 不会有隐式转换,所以用 defineProperty 劫持就好了

1
2
3
4
5
6
7
8
// defineProperty
const value = 1
Object.defineProperty(window, 'a', {
get() {
return value++
},
})
console.log(a === 1 && a === 2 && a === 3)