通过面试题 a == 1 和 a == 2 以及 a == 3 你看到了什么?
一、背景
上周回家路上,逛社区给我推了这样一道「面试题」,要使其结果为 true
,如何实现?
a == 1 && a == 2 && a == 3
心想不是很简单嘛,考察的是数据类型转换的知识。自实现一个 @@toPrimitive
方法就行,思路很简单。换句话说:实现这样一个方法,在每次调用时返回值增加 1
。这样大家闭眼就能写出来了:
const a = { [Symbol.toPrimitive]() { if (this._val === undefined) this._val = 0 this._val++ return this._val } } console.log(a == 1 && a == 2 && a == 3) // true
这道题它的属性名 [Symbol.toPrimitive]
比较少用少见,仅此而已。
其中
@@toPrimitive
方法只能通过计算属性[Symbol.toPrimitive]
的方式去表示。类似的还有很多,比如实现或重写迭代器的@@iterator
方法表示为[Symbol.iterator]
。
二、为什么?
这会该有人跳出来吐槽:实际项目中毫无意义,怎么还有面试官问这种傻逼问题?我尝试换位思考,面试官如何在短时间内判断一个候选人的专业技能?通过这些看似奇葩,但综合性较强的题目来考察不失为一种好方法(这题好像不太强)。
私以为,学好一个知识点,应做到「深入了解,举一反三」。
加个菜,若要使下面同时成立(临时想出来的),你还会做吗?
console.log(a == 1 && a == 2 && a == 3) // true console.log(+a) // 100 console.log(`${a}`) // 1000
其实也很简单,原因是 @@toPrimitive
方法在执行时,会接收到一个 hint
参数,其值为 default
、string
、number
之一。然后你又能立刻实现出来了:
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 console.log(+a) // 100 console.log(`${a}`) // 1000
私以为,不能做到举一反三,可能是没有完全掌握相关知识。这样的话,应该找时间去学习一下。
三、进一步深入?
在 ECMAScript 标准中,常见数据类型转换操作有这些:
- ToPrimitive - 转换为原始值
- ToBoolean - 转换为布尔值
- ToNumber - 转换为数值
- ToString - 转换为字符串
- ToObject - 转换为引用值
注意,以上这些并不是 JavaScript 里面具体的方法,而是 ECMAScript 标准中的抽象操作(Abstract Operations)。其实不止这些,ECMAScript 标准还有很多转换方法,更多请看 Type Conversion 章节。如果你是第一次阅读 ECMAScript 标准,难免会有种羞涩难懂的感觉,这里推荐阮一峰老师读懂规范一文,或许有帮助。
此前其实已经写过一篇关于 JavaScript 数据类型转换的文章,里面也很详细地介绍了,可移步至:数据类型转换详解。还写下这篇文章其实是想「温故知新」。另外,我想读者可能更喜欢从一个实际问题出发,然后再深入其背后的原理。
就文章开头的题目而言,这里明显就是「ToPrimitive」操作了,给大家看下 ECMAScript 是如何定义(详见):
第一次阅读的时候,也曾感慨「不愧是抽象操作,确实很抽象」,但反复阅读下来之后,也不会很难。
本文将不会逐行逐字进行翻译,此前写那篇文章 数据类型转换详解 已经做了这件事。
以 ToPrimitive(obj, hint)
为例
如果
obj
为原始值,直接返回该值。如果
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 == 1 && a == 2 && a == 3
令 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"
个人猜测:「显式转换」为字符串或数组时,对应为
"string"
或"number"
,其他情况为"default"
。
还有一个略麻烦的方法,在规范中通过 References 中一个一个找到引用此算法的其他操作,然后一层一层去查哪些方法有使用了这种操作,然后总结列出来。
四、其他
此前写过不少与 JavaScript 数据类型相关的文章,如果对此不熟的童鞋,可以看一看:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 细谈设计模式
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论