我正在参与掘金创作者训练营第6期,点击了解活动详情
前言
来了,来了,这”讨厌的面试官“。只见他自认帅气的摸了摸他的地中海发型,心里犯嘀咕:上次面试的那个家伙说的 webpack5(前景回顾),居然没难到他,这次看我这家伙身上找回面子。再次张开了他那”性感的厚嘴唇”说到:“准备好了吗?我们来聊聊valueOf & toString & toPrimitive吧。“
基本概念
valueOf: 返回对象的原始值
toString: 返回对象的字符串
toPrimitive: 一个内置的 Symbol 值,它是作为对象的函数值属性存在的,如果对象中存在个属性时,当一个对象转换为对应的原始值时,会优先调用此函数
细说规则
valueOf
对于基础类型的数据,直接返回该类型
boolean null undefined string number symbol
对于非原始值的重写规则如下
| 对象 | valueOf的返回值 |
|---|---|
| Array | 数组本身 |
| Boolean | 布尔值 |
| Date | 返回毫秒形式的时间戳 |
| Function | 函数本身 |
| Number | 数字值 |
| Object | 对象本身 |
| String | 字符串值 |
toString转换规则
| 对象 | toString的返回值 |
|---|---|
| Array | 以逗号分割的字符串,如[1,2]的toString返回值为"1,2" |
| Boolean | True |
| Date | 可读的时间字符串,如"Tue Oct 15 2019 12:20:56 GMT+0800 (中国标准时间)" |
| Function | 声明函数的JS源代码字符串 |
| Number | "数字值" |
| Object | "[object Object]" |
| String | "字符串" |
这里附带一个小知识点 toString 与 String 的异同?
相同点:都是将一个值转换成字符串
区别:
- toString可以传参,表示以多少位的格式输出结果;String方法传参无效
- null和undefined不能调用toString,而String可以转换'null'和'undefined'
toPrimitive转换规则
[Symbol.toPrimitive](hint){
console.log(hint);
}
hint 并不需要我们手动传入,而是JS根据上下文自动判断。hint 参数的只能 string,number,default三者之一。
"string"
对象到字符串的转换,当我们对期望一个字符串的对象执行操作时,如 “alert”
"number"
// 显式转换
let num = Number(obj);
// 数学运算(除了二元加法)
let n = +obj;
// 一元减法
let delta = date1 - date2;
// 小于/大于的比较
let greater = user1 > user2;
// 乘法
let multiplication = user1 * 2;
"default"
在少数情况下发生,当运算符“不确定”期望值的类型时。 例如,二元加法 + 可用于字符串(连接),也可以用于数字(相加),所以字符串和数字这两种类型都可以。因此,当二元加法得到对象类型的参数时,它将依据 "default" hint 来对其进行转换。 此外,如果对象被用于与字符串、数字或 symbol 进行 == 比较,这时到底应该进行哪种转换也不是很明确,因此使用 "default" hint。
举些例子
valueOf 、 toString 、 toPrimitive 同时存在
let user = {
name: '面试官',
money: 1000,
toString() {
return this.name
},
valueOf() {
return this.money
},
[Symbol.toPrimitive](hint) {
return hint == 'string' ? this.name : this.money
}
}
console.log(`${user}`) // hint=string 面试官
console.log(+user) // hint=number 1000
console.log(user + 10) // hint=default 1010
valueOf 、 toString 同时存在
let user = {
name: '面试官',
money: 1000,
toString() {
return this.name
},
valueOf() {
return this.money
}
}
console.log(`${user}`) // 面试官
console.log(+user) // 1000
valueOf 、 toString 任意一个返回非原始值
let user = {
name: '面试官',
money: 1000,
toString() {
return {}
},
valueOf() {
return this.money
}
}
console.log(`${user}`) // '1000'
console.log(+user) // 1000
只有 valueOf
const user = Object.create(null)
user.name = 'John'
user.money = 1000
user.valueOf = function () {
return user.money
}
console.log(`${user}`) // '1000'
console.log(+user) // 1000
这个地方要停下来说一下,为什么我们要用Object.create的方式创建user对象? 因为如果是用字面量的方式声明user,虽然没有显示声明 toString() 方法,但其原型链上存在 toString() 方法,此次这里是为了创建一个纯对象,排除原型链的干扰。
下个结论
在JavaScript的运算过程中,必须是对原始值进行运算,再得到运算结果,比如 obj1+obj2 不能是一个新的对象,因此这里就涉及到当对象参与运算时,得先有一个转换过程!
对于对象类型的数据进行转换时
- 如果定义了 Symbol.toPrimitive 方法,则优先调用;
- 如果 hint 为 string 时,则优先调用toString()方法,如没有或返回的是非原始值再调用valueOf()方法;
- 如果 hint 为 number或default 时,则优先调用valueOf()方法,如没有或返回的是非原始值再调用toString()方法;
以上结论都是建立在该对象具有 toString()、valueOf()、Symbol.toPrimitive 三者方法任意一种存在。
另外:
- Symbol.toPrimitive 不能返回非原始值,否则报错;
- 如果是 Object.create(null) 定义的对象,尝试转换,那么会直接抛错;
- 万能转换方法:如果要自定义转换结果,其实只需要定义一个 toString 方法即可;
实战练习
参数累加
期望有一个函数 curry(1, 2)(3, 4, 5)(6)(7, 8) ,执行后得到累加的效果。
function curry(...agrs1) {
let arr = [];
const sum = function sum(...args2) {
arr = [...agrs1, ...args2];
return sum;
};
sum.__proto__[Symbol.toPrimitive] = function (hint) {
return arr.reduce((t, c) => (t += c), 0);
};
return sum;
}
const res = curry(1, 2)(3, 4, 5)(6)(7, 8);
console.log(+res);
实现 a== 1&&a==2&&a==3 为 true 与 a===1&&a===2&&a===3 为 true
看到这个题目是不是脑瓜子嗡嗡的?其实如果理解到了上面说到的知识点,也不是没有思路
首先 对于 == 会进行隐式转换
class A {
constructor(v) {
this.a = v
}
valueOf() {
return ++this.a
}
}
var a = new A(0)
console.log(a == 1 && a == 2 && a == 3) // == 会进行隐式转换,hint为default
=== 在严格模式下不会进行隐式转换,因此在这里我们需要进行一些特殊处理
var value = 1;
Object.defineProperty(window, 'a', {
get() {
return value++
}
})
console.log(a === 1 && a === 2 && a === 3)
最后
完了,完了。”这家伙怎么也答得上来?我还能保持我大佬的形象吗?“ 我们”讨厌的面试官“心想。故作镇定的说道:”回答得七七八八吧,但有瑕疵哈,有瑕疵,让底下观众告诉你吧!来呀,准备下一场面试“
各位吃瓜群众,欢迎指正哈!让这”面试官“知道山外有山!!