如果 obj 为引用值,且含有 @@toPrimitive 方法(必须是函数),然后
a. 若参数 hint 不存在,令 hint 为 "default";若 hint 为字符串,令 hint 为 "string";否则令 hint 为 "number"。
b. 调用 @@toPrimitive 方法,若其返回值为「原始值」,则返回其值,否则抛出 TypeError。
若参数 hint 不存在,令 hint 为 "number";
当 hint 为 "string",将会先后调用 obj 的 toString()、valueOf() 方法,然后
a. 若 toString 为函数,则调用该方法。若其返回值为「原始值」,则返回该值,否则进行往下。
b. 若 valueOf 为函数,则调用该方法。若其返回值为「原始值」,则返回该值,否则抛出 TypeError。
当 hint 为 "number",将会先后调用 obj 的 valueOf()、toString() 方法,然后
a. 若 valueOf 为函数,就调用该方法。若其返回值为「原始值」,则返回该值,否则进行往下。
b. 若 toString 为函数,就调用该方法。若其返回值为「原始值」,则返回该值,否则抛出 TypeError。
目前 ECMAScript 内置的对象中,只有 Date 和 Symbol 对象有实现其 @@toPrimitive 方法。因此,其他内置对象无非就是根据 valueOf() 或 toString() 方法,得到其转换结果罢了,所以数据类型转换没那么神秘。
令 a 成为一个引用值,比如对象。这时,除了实现 @@toPrimitive 方法,还可以改写其 toString() 方法去达到目的。
const a = {
toString() {
if (this._val === undefined) this._val = 0
this._val++
return this._val
}
}
console.log(a == 1 && a == 2 && a == 3) // true
但如果要使成立,只能利用 @@toPrimitive 方法会传入 hint 参数的特点去实现了:
console.log(a == 1 && a == 2 && a == 3) // true
console.log(+a) // 100
console.log(`${a}`) // 1000
也就是
const a = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') return 100
if (hint === 'string') return 1000
if (this._val === undefined) this._val = 0
this._val++
return this._val
},
}
console.log(a == 1 && a == 2 && a == 3) // true,此时 hint 为 "default"
console.log(+a) // 100,此时 hint 为 "number"
console.log(`${a}`) // 1000,此时 hint 为 "string"
那么,这个 hint 有何规律呢?实在惭愧,我也没太琢磨出来。常见操作的 hint 如下:
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 1
} else if (hint === 'string') {
return 'string'
} else {
return 'default'
}
}
}
console.log(+obj) // 1 hint is "number"
console.log(Number(obj)) // 1 hint is "number"
console.log(`${obj}`) // "string" hint is "string"
console.log(String(obj)) // "string" hint is "string"
console.log(obj + '') // "default" hint is "default"
console.log(obj + 1) // "default1" hint is "default"
一、背景
上周回家路上,逛社区给我推了这样一道「面试题」,要使其结果为
true
,如何实现?心想不是很简单嘛,考察的是数据类型转换的知识。自实现一个
@@toPrimitive
方法就行,思路很简单。换句话说:实现这样一个方法,在每次调用时返回值增加1
。这样大家闭眼就能写出来了:这道题它的属性名
[Symbol.toPrimitive]
比较少用少见,仅此而已。二、为什么?
这会该有人跳出来吐槽:实际项目中毫无意义,怎么还有面试官问这种傻逼问题?我尝试换位思考,面试官如何在短时间内判断一个候选人的专业技能?通过这些看似奇葩,但综合性较强的题目来考察不失为一种好方法(这题好像不太强)。
加个菜,若要使下面同时成立(临时想出来的),你还会做吗?
其实也很简单,原因是
@@toPrimitive
方法在执行时,会接收到一个hint
参数,其值为default
、string
、number
之一。然后你又能立刻实现出来了:私以为,不能做到举一反三,可能是没有完全掌握相关知识。这样的话,应该找时间去学习一下。
三、进一步深入?
在 ECMAScript 标准中,常见数据类型转换操作有这些:
注意,以上这些并不是 JavaScript 里面具体的方法,而是 ECMAScript 标准中的抽象操作(Abstract Operations)。其实不止这些,ECMAScript 标准还有很多转换方法,更多请看 Type Conversion 章节。如果你是第一次阅读 ECMAScript 标准,难免会有种羞涩难懂的感觉,这里推荐阮一峰老师读懂规范一文,或许有帮助。
就文章开头的题目而言,这里明显就是「ToPrimitive」操作了,给大家看下 ECMAScript 是如何定义(详见):
第一次阅读的时候,也曾感慨「不愧是抽象操作,确实很抽象」,但反复阅读下来之后,也不会很难。
本文将不会逐行逐字进行翻译,此前写那篇文章 数据类型转换详解 已经做了这件事。
以
ToPrimitive(obj, hint)
为例目前 ECMAScript 内置的对象中,只有
Date
和Symbol
对象有实现其@@toPrimitive
方法。因此,其他内置对象无非就是根据valueOf()
或toString()
方法,得到其转换结果罢了,所以数据类型转换没那么神秘。然后,回到文章开头那道题:
令
a
成为一个引用值,比如对象。这时,除了实现@@toPrimitive
方法,还可以改写其toString()
方法去达到目的。但如果要使成立,只能利用
@@toPrimitive
方法会传入hint
参数的特点去实现了:也就是
那么,这个
hint
有何规律呢?实在惭愧,我也没太琢磨出来。常见操作的hint
如下:还有一个略麻烦的方法,在规范中通过 References 中一个一个找到引用此算法的其他操作,然后一层一层去查哪些方法有使用了这种操作,然后总结列出来。
四、其他
此前写过不少与 JavaScript 数据类型相关的文章,如果对此不熟的童鞋,可以看一看:
The end.