# 从规范角度剖析对象的原始值转换
当使用
+
运算符计算时,如果存在复杂数据类型,那么它将会被转换为基本数据类型进行计算。转换时会调用该对象上的valueOf/toString
方法,这两个方法的返回值是转换后的结果。具体是调用valueOf
还是toString
。这取决内置的toPrimitive
的调用结果。从主观上来说对象如果倾向转为number
类型就调用valueOf
,否则调用toString
。
valueOf/toString
可以被开发者重写
const foo = {
toString() {
return 'lucas'
},
valueOf() {
return 1
}
}
2
3
4
5
6
7
8
TIP
这时候调用alert(foo)
将输出lucas
。因为alert()
接收一个字符串参数。如果执行console.log(1+foo)
将输出2
。
总结:valueOf
偏向于运算,toString
偏向于显示。
- 在进行对象转换时,将优先调用
toString
方法,如若没有重写toString
,将调用valueOf
方法;如果两个方法都没有重写,则按Object
的toString
输出。 - 在进行强转字符串类型时,将优先调用
toString
方法,强转为数字时优先调用valueOf
- 使用运算操作符的情况下,
valueOf
的优先级高于toString
。
# Symbol.toPrimitive
Symbol.toPrimitive
是一个内置的Symbol
值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。
class A {
constructor(count) {
this.count = count
}
valueOf() {
return 2
}
toString() {
return '哈哈哈'
}
// 我在这里
[Symbol.toPrimitive](hint) {
if (hint == "number") {
return 10;
}
if (hint == "string") {
return "Hello Libai";
}
return true;
}
}
const a = new A(10)
console.log(`${a}`) // 'Hello Libai' => (hint == "string")
console.log(String(a)) // 'Hello Libai' => (hint == "string")
console.log(+a) // 10 => (hint == "number")
console.log(a * 20) // 200 => (hint == "number")
console.log(a / 20) // 0.5 => (hint == "number")
console.log(Number(a)) // 10 => (hint == "number")
console.log(a + '22') // 'true22' => (hint == "default")
console.log(a == 10) // false => (hint == "default")
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
比较特殊的是
+
拼接符,这个属于default
模式
- 该方法作用同
valueOf/toString()
一样,但是优先级高于这两个 - 该函数被调用时,会被传递一个字符串参数
hint
。string
:字符串类型number
:数字类型default
:默认
# 面试题分析
完美呈现出toString/valueOf
的作用
a===1&&a===2&&a===3
什么时候为true
双等号
(==)
:会触发隐式类型转换,所以可以使用valueOf
或者toString
来实现。每次判断都会触发valueOf
方法,同时让value+1
,才能使得下次判断成立。
class A {
constructor(value) {
this.value = value;
}
valueOf() {
return this.value++;
}
}
const a = new A(1);
if (a == 1 && a == 2 && a == 3) {
console.log("Hi Libai!");
}
2
3
4
5
6
7
8
9
10
11
12
全等(===)
:严格等于不会进行隐式转换,这里使用 Object.defineProperty
数据劫持的方法来实现
let value = 1;
Object.defineProperty(window, 'a', {
get() {
return value++
}
})
if (a === 1 && a === 2 && a === 3) {
console.log("Hi Libai!")
}
2
3
4
5
6
7
8
9
- 实现一个无限累加函数
add(1) // 1
add(1)(2) // 3
add(1)(2)(3) // 6
add(1)(2)(3)(4) // 10
// 以此类推
function add(a) {
function sum(b) { // 使用闭包
a = b ? a + b : a; // 累加
return sum;
}
sum.toString = function() { // 只在最后一次调用
return a;
}
return sum; // 返回一个函数
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
add
函数内部定义sum
函数并返回,实现连续调用sum
函数形成了一个闭包,每次调用进行累加值,再返回当前函数sum
add()
每次都会返回一个函数sum
,直到最后一个没被调用,默认会触发toString
方法,所以我们这里重写toString
方法,并返回累计的最终值a
TIP
add(10)
: 执行函数add(10)
,返回了sum
函数,注意这一次没有调用sum
,默认执行sum.toString
方法。所以输出10
;
add(10)(20)
: 执行函数add(10)
,返回sum
(此时a
为10
),再执行sum(20)
,此时a
为30
,返回sum
,最后调用sum.toString()
输出30
。add(10)(20)...(n)
依次类推。
- 柯里化实现多参累加
add(1)(3,4)(3,5) // 16
add(2)(2)(3,5) // 12
function add(){
// 1 把所有参数转换成数组
let args = Array.prototype.slice.call(arguments)
// 2 再次调用add函数,传递合并当前与之前的参数
let fn = function() {
let arg_fn = Array.prototype.slice.call(arguments)
return add.apply(null, args.concat(arg_fn))
}
// 3 最后默认调用,返回合并的值
fn.toString = function() {
return args.reduce(function(a, b) {
return a + b
})
}
return fn
}
// ES6写法
function add () {
let args = [...arguments];
let fn = function(){
return add.apply(null, args.concat([...arguments]))
}
fn.toString = () => args.reduce((a, b) => a + b)
return fn;
}
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