# 从规范角度剖析对象的原始值转换
当使用
+运算符计算时,如果存在复杂数据类型,那么它将会被转换为基本数据类型进行计算。转换时会调用该对象上的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函数形成了一个闭包,每次调用进行累加值,再返回当前函数sumadd()每次都会返回一个函数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