本文需要先了解 Symbol
概述
- 所有对象在布尔上下文中均为 true。所以,对象不存在布尔值转换问题。只有字符串和数值转换。
- 数值转换:对象相减
-
或者应用数学函数时。例如两个 Date 对象相减。 - 字符串转换,发生在期望需要字符串的上下文时,例如 alert 函数。
转换变体
在转换时,hint 用来指代具体哪种原始值的上下文。
下面是三个类型转换的变体,hint 值分别为:
"string"
。对象到字符串的转换,当我们对期望一个字符串的对象执行操作时. 例如 alert 方法"number"
。对象到数字的转换,例如进行数学运算(** 二元加法除外 **)时。"default"
。只在少数情况下发生。当不能仅仅由运算符确定期望的类型时。例如,二原加运算符在有字符串参与运算时,可以连接字符串,在数学运算时,可以执行加法。此时 hint 就是 default。还有一种情况是,对象使用==
与字符串,数字,或者 symbol 比较时。也是用 default 的 hint。
大于,小于虽然也算不确定情况(既可以比较字符串也可以比较数字),但是由于历史原因,对象参与这两个运算时的 hint 是 “number”。
三个对象方法
上面说的 hint 还要看对象有没有对应的方法:
- 若存在
实例对象[Symbol.toPrimitive](hint)
方法,则根据上面的描述决定传入的 hint 是"string", "number" 还是 "default"
- 不存在上面的 1 的方法,且 hint 是
"string"
,则尝试实例对象. toString()
和实例对象. valueOf()
- 不存在上面的 1 的方法,且 hint 是
number 或者 default
,尝试实例对象. valueOf()
和实例对象. toString()
Symbol.toPrimitive
此内建 symbol 用来给 ** 转换方法 ** 命名:
let obj = {age:999,name:'路由器'};
obj + 10; =>'[object Object]10'
obj[Symbol.toPrimitive] = function(hint) {
console.log(hint);
return hint === 'string' ? this.name : this.age;
}
obj + 10; => hint:number
obj + "10"; => hint:default
+obj; => hint:default
obj > 9; => hint:number
obj <= 1000; => hint:number
可以看出,obj[Symbol.toPrimitive]
根据转换的上下文的不同,obj 可以是字符串,也可以是数值。而且一般做法是将 default 和 number
视作同一个情况。
此
Symbol.toPrimitive
方法必须返回原始值(number, string,boolean,null, undefined
),否则在进行类型转换时会报错!!!
toString&valueOf
还没有 symbol 的时候就有了这两个方法,用来实现转换。
就像上面说的,没有 Symbol.toPrimitive
时,JavaScript 尝试使用这两个方法。
顺序是:
- 对于
"string"
的hint
,toString 优先于 valueOf - 其他情况,valueOf 优先于 toString
默认情况下,普通对象具有 toString
和 valueOf
方法:
toString
方法默认返回一个字符串 “[object Object]”valueOf
方法默认返回对象本身
测试执行顺序:
let obj = {
toString() {
return "2";
}
};
obj * 4 => 8
这是 number 上下文,首先寻找 valueOf 找不到,toString 找到了,就回去执行 toString,得到字符串,然后尝试 string 到 number 转换。
在期望需要字符串的上下文时:
let user = {name: "John"};
alert(user); // [object Object]
alert(user.valueOf() === user); // true
所以,如果我们尝试将一个对象当做字符串来使用,例如在 alert 中,那么在默认情况下我们会
看到 [object Object]
。
自己实现这两个方法时,需要注意:
必须返回原始值,否则对象在需要进行类型转换时,你的非原始类型的返回值变得毫无意义。
现在,实现一个同前面的 obj[Symbol.toPrimitive]
一样功能的方法:
let obj2 = {
age: 666,
desp: '描述',
}
obj2.toString = function () {
return this.desp;
}
obj2.valueOf = function () {
return this.age;
}
多重转换
- 对象被转换为原始值(通过前面我们描述的规则)。
- 如果生成的原始值的类型不正确,则继续进行转换。
总结
通常对于内建对象, “default” hint 的处理方式与 “number” 相同,因此在实践中,最后两个 hint 常常合并在一起。
转换算法:
- 调用
obj[Symbol.toPrimitive](hint)
如果这个方法存在, - 否则,如果 hint 是 “string”
- 先尝试
obj.toString()
,如果 toString 的返回值为原始类型,则得到此值就此结束,如果返回值为其他类型,则返回对象本身。 - 没有
obj.toString()
则去调用 valueOf, 如果 valueOf 的返回值为原始类型,则得到此值就此结束,如果返回值为其他类型,则返回对象本身
- 先尝试
- 否则,如果 hint 是 number,则先尝试 valueOf,再尝试 toString。
在实践中,为了便于进行日志记录或调试,对于所有能够返回一种 “可读性好” 的对象的表达形式的
转换,只实现以 obj.toString() 作为全能转换的方法就够了
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。