JS 对象到原始值的转换
背景
工作中遇到一个 Long.js 对象,它通过对象的方式存一个 Long
类型的数据。大致长这样:
1 | interface Long { |
用对象去存数值,自然就涉及到怎么用基本数据类型表示它的问题,也就是说需要把对象转换为 number
或者 string
等类型
ES6 的现代方案
Symbol.toPrimitive
ES6 为对象新增了许多内置方法,Symbol.toPrimitive
是其中之一。当对象转为原始值时,会调用这个方法。
1 | const a = { |
什么时候会被转为原始值
这里有个很模糊的概念,对象什么时候会被转为原始值?
没有找到特别明确的范围,只能归纳一些经典场景:
- 被算术运算符作用时,如
+
-
,需要转为number
window.alert
、String()
、模板字符串`${obj}`
,需要转为string
!
需要转为boolean
,当然,对象转为boolean
都为true
剩下的基本数据类型中,null
、undefined
、Symbol
显然是没有转换的意义的
hint
可见把对象转为基本数据类型,需要关注具体转为 number
还是 string
。
Symbol.toPrimitive
这个方法会有一个入参,被称为 hint
,具体值可以是 'number'
、'string'
或者 'default'
。
可以认为 hint
是语言内置的「提示」,在上述场景下调用 Symbol.toPrimitive
时,JS 会自动传入对应的 hint
。
如果 JS 认为是字符串,就会传 'string'
,如果认为是数值,就会传 'number'
,如果有些场景既可能是字符串,有可能是数值,无法判断,就会传 'default'
开发者可以根据 hint
定制对象在特定的转换场景下的行为
1 | const obj = { |
ES6 之前的方案
Symbol.toPrimitive
是 ES6 之后的解决方案。在此之前,已有的对象通过实现 valueOf
和 toString
方法,来转换成原始值。
值得注意的是,hint
的概念是一直存在的,Symbol.toPrimitive
只是一个显式的获取 hint
的途径。
注意,如果 hint
为 'string'
会调用对象的 toString
,其它 hint
会调用 valueof
,这里没有 default
的概念
valueOf 与 toString
Object
的 prototype
里定义了 valueOf
和 toString
两个方法,可供后续覆写——由对象自定义转换为原始值时的行为。
对原型来说,Object.prototype.valueOf
会返回对象自身;Object.prototype.toString
会返回 '[object type]'
,比如 '[object Object]'
实践:为什么 Date 对象可以做相减操作
JS 没有类似 C++ 那样的运算符重载的能力,对象之间相减,会尝试把它们转换为 number 后,再做计算。
Date.prototyle.valueOf
会返回自身的 Unix 时间戳,因此两者的减法,就是相对时间的长度了。
兜底与多次转换
通过 Symbol.toPrimitive
、valueOf
、toString
转换原始值,需要这些方法返回原始值,否则,就会尝试别的方法来转换。例如对象原生的 valueOf
返回自身,是一个对象,就不能用于原始值的转换,就会尝试 toString
。
转换的算法是:
- 如果
Symbol.toPrimitive
存在,调用它 - 不存在或者返回值不是原始值:
- 如果 hint 是 ‘string’:尝试调用
toString
,如果返回值不是原始值,尝试调用valueOf
- 如果 hint 是 ‘number’ 或者 ‘default’:尝试调用
valueOf
,如果返回值不是原始值,尝试调用toString
- 如果 hint 是 ‘string’:尝试调用
1 | const a = { |
- 没有
Symbol.toPrimitive
,因为是一元加法,hint
为'number'
,兜底到valueOf
; valueOf
默认返回a
自身,不是原始值,兜底到toString
;- 返回
string
后,+"123"
表达式就是一个基本数据类型的转换了,得到number
2022.9.13 补充
在测试中又碰到几个对象字面量
在运算中的奇妙现象
1 | {} + 1 // 1 |
这是由于引擎对 {}
这个词法的解析,有二义性:Block
和 Object
两种语法。
通过 ()
或者变量引用的方式,引擎才能正确把它解析为对象,然后调用 toString
方法。
当然,这就涉及到经典的 {} + {}
、{} + []
这类特色语法问题了,在此不提。