对象原始值转换
摘要:
对象隐式类型转换规则:Symbol.toPrimitive、valueOf、toString
先看看下方的代码会怎样执行
js
const obj1 = {}
const obj2 = {}
const compare = obj1 > obj2 || obj1 < obj2 || obj1 == obj2 // false
const compare = obj1 >= obj2 && obj1 <= obj2 // true
const num = +obj1 // NaN
const add = obj1 + obj2 // [object Object][object Object]
const minus = obj1 - obj2 // NaN
obj2[obj1] = ''; // {"[object Object]": ''}
为什么会出现上面的情况,js又是如何处理的呢?首先需要了解一个概念“hint”。
hint
类型转换在各种情况下有三种变体。它们被称为
“hint”
string
对象到字符串的转换,当我们对期望一个字符串的对象执行操作时,如 “alert”
:
js
// 输出
alert({});
// 将对象作为属性键
anotherObj[{}] = 123;
// 显示调用
({}).toString()
String({})
number
当我们进行数学运算时(二元加法除外)
js
// 显式转换
const obj = {}
let num = Number(obj);
// 数学运算(除了二元加法)
let n = +obj; // 一元加法
let delta = date1 - date2;
// 小于/大于的比较
let greater = user1 > user2;
default
当到底应该进行哪种转换不是很明确时。(比如二元加法,或者===比较),这些可以同时用于字符串和数字的操作。
js
const obj1 = {}
const obj2 = {}
const total = obj1 + obj2
if(obj1 === 1) {}
像 <
和 >
这样的小于/大于比较运算符,也可以同时用于字符串和数字。不过,它们使用 “number” hint,而不是 “default”。这是历史原因。
上面这些规则看起来比较复杂,但在实践中其实挺简单的。
除了一种情况(Date
对象)之外,所有内建对象都以和 "number"
相同的方式实现 "default"
转换。我们也可以这样做。
了解了上面这些,我们就可以自主操纵转换行为了
自定义转换方法及优先级
1、调用 obj[Symbol.toPrimitive](hint)
—— 带有 symbol
键 Symbol.toPrimitive
(系统 symbol)的方法,如果这个方法存在的话,
2、否则,如果 hint
是 "string"
—— 尝试调用 obj.toString()
或 obj.valueOf()
,无论哪个存在。
3、否则,如果 hint
是 "number"
或 "default"
—— 尝试调用 obj.valueOf()
或 obj.toString(),无论哪个存在。
Symbol.toPrimitive
有了此方法,toString和valueOf方法在隐式转换时就不会再被调用了。
js
const user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
return hint == "string" ? JSON.stringify(this) : this.money;
}
};
const a = String(user) // '{"name":"John","money":1000}'
const b = user - 1 // 999
toString/valueOf
如果没有
Symbol.toPrimitive
,那么 JavaScript 将尝试寻找toString
和valueOf
方法:
- 对于 "string" hint:调用
toString
方法,如果它不存在,则调用valueOf
方法(因此,对于字符串转换,优先调用 toString)。 - 对于其他 hint:调用
valueOf
方法,如果它不存在,则调用toString
方法(因此,对于数学运算,优先调用valueOf
方法)。
toString
和 valueOf
方法很早己有了。它们不是 symbol(那时候还没有 symbol 这个概念),而是“常规的”字符串命名的方法。它们提供了一种可选的“老派”的实现转换的方法。
这些方法必须返回一个原始值。如果 toString
或 valueOf
返回了一个对象,那么返回值会被忽略(和这里没有方法的时候相同)。
默认情况下,普通对象具有 toString
和 valueOf
方法:
toString
方法返回一个字符串 "[object Object]"
。
valueOf
方法返回对象自身。
所以,如果我们尝试将一个对象当做字符串来使用,例如在 alert
中,那么在默认情况下我们会看到 [object Object]
。
这里提到的默认的 valueOf
只是为了完整起见,以避免混淆。正如你看到的,它返回对象本身,因此被忽略。别问我为什么,这是历史原因。所以我们可以假设它根本就不存在。
js
const user = {
name: "John",
money: 1000,
// 对于 hint="string"
toString() {
return JSON.stringify(this);
},
// 对于 hint="number" 或 "default"
valueOf() {
return this.money;
}
};
const a = String(user) // '{"name":"John","money":1000}'
const b = user - 1 // 999
由于历史原因,如果
toString
或valueOf
返回一个对象,则不会出现 error,但是这种值会被忽略(就像这种方法根本不存在)。这是因为在 JavaScript 语言发展初期,没有很好的 “error” 的概念。相反,
Symbol.toPrimitive
更严格,它 必须 返回一个原始值,否则就会出现 error。
解释开头的代码
js
const obj1 = {}
const obj2 = {}
// 这里使用比较运算符,那么hint的类型为number,会调用valueOf方法,
// 但valueOf返回的对象类型会被忽略,然后调用toString,返回 "[object Object]"
// 对象的是否相等比较就不转换了,直接比较对象的引用地址是否相同,只有对象和原始类型比较相等才会转换。
const compare = obj1 > obj2 || obj1 < obj2 || obj1 == obj2 // false
const compare = obj1 >= obj2 && obj1 <= obj2 // true
// 相当于 Number("[object Object]")
const num = +obj1 // NaN
// "[object Object]" + "[object Object]"
const add = obj1 + obj2 // [object Object][object Object]
// "[object Object]" - "[object Object]"
const minus = obj1 - obj2 // NaN
// obj2["[object Object]"] = ''
obj2[obj1] = ''; // {"[object Object]": ''}
一个恶趣味的面试题
实现变量a,满足
console.log(a == 1 && a == 2 && a == 3)
结果为true。
js
const a = {
value: 0,
[Symbol.toPrimitive]() {
return ++this.value
}
}
console.log(a == 1 && a == 2 && a == 3) // true
评论
0条评论
暂无内容,去看看其他的吧~