本文将介绍ECMAScript7
规范中的ToPrimitive
抽象操做。算法
ECMAScript
数据类型细分为两大类数据类型,一种是语言类型,一种是规范类型:数组
meta-values
(元值),用在算法中描述ECMAScript
语言结构和语言类型的语义。它们主要用于规范的说明,不须要被真正地实现。ECMAScript
的语言类型一共有7种:this
原始数据类型是上述Undefined
、Null
、Boolean
、String
、Symbol
和Number
的统称,也就是非对象数据类型。
下文涉及到的规范类型只有List
,也就是列表,相似于数组,用符号« »
表示。lua
Symbol
有不少有名的符号,好比@@toPrimitive
,也就是Symbol.toPrimitive
,这是定义在Symbol
对象上的一个属性。prototype
该抽象操做接受一个参数input
和一个可选的参数PreferredType
。该抽象操做的目的是把参数input
转化为非对象数据类型,也就是原始数据类型。若是input
能够同时转化为多个原始数据,那么会优先参考PreferredType
的值。转化过程参照下表:code
参数input 的数据类型 |
结果 |
---|---|
Undefined | 返回input自身 |
Null | 返回input自身 |
Boolean | 返回input自身 |
Number | 返回input自身 |
String | 返回input自身 |
Symbol | 返回input自身 |
Object | 执行下面的步骤 |
若是input
的数据类型是对象,执行下述步骤:对象
PreferredType
参数,让hint
等于"default"
;PreferredType
是hint String
,让hint
等于"string"
;PreferredType
是hint Number
,让hint
等于"number"
;exoticToPrim
等于GetMethod(input, @@toPrimitive)
,大概语义就是获取参数input
的@@toPrimitive
方法;若是exoticToPrim
不是Undefined
,那么:ip
result
等于Call(exoticToPrim, input, « hint »)
,大概语义就是执行exoticToPrim(hint)
;result
是原始数据类型,返回result
;hint
是"default"
,让hint
等于"number"
;OrdinaryToPrimitive(input, hint)
抽象操做的结果。O
的数据类型是对象,hint
的数据类型是字符串,而且hint
的值要么是"string"
,要么是"number"
。该抽象操做的步骤以下:原型链
hint
是"string"
,让methodNames
等于« "toString", "valueOf" »
;hint
是"number"
,让methodNames
等于« "valueOf", "toString" »
;按顺序迭代列表methodNames
,对于每个迭代值name
:开发
method
等于Get(O, name)
,大概语义就是获取对象O
的name
值对应的属性;若是method
能够调用,那么:
method
等于Call(method, O)
,大概语义就是执行method()
;result
的类型不是对象,返回result
;由上述操做步骤可知:
ToPrimitive
的步骤6
可知,当没有提供可选参数PreferredType
的时候,hint
会默认为"number"
;ToPrimitive
的步骤4
可知,能够经过定义@@toPrimitive
方法来覆盖默认行为,好比规范中定义的Date
日期对象和Symbol
符号对象都在原型上定义了@@toPrimitive
方法。可能有人会问,为何要讲解规范中的抽象方法,抽象方法我又用不到。其实否则,这个方法在不少地方都会用到,只是你不知道罢了。下面经过讲解几个实例让你们加深对它的理解。
'' + [1, 2, 3] // "1,2,3"
根据规范中的加法操做,对于操做x + y
,会调用ToPrimitive(x)
和ToPrimitive(y)
把x
和y
转化为原始数据类型。上面的例子中''
自己就是原始数据类型了,因此返回''
自身。[1, 2, 3]
是对象类型,而且数组没有定义@@toPrimitive
属性。由于没有提供PreferredType
,因此在ToPrimitive
操做的步骤6
中,hint
变为"number"
,因此OrdinaryToPrimitive
中的methodNames
是« "valueOf", "toString" »
。
var a = [1, 2, 3] a.valueOf() // [1, 2, 3],数组a自己 a.toString() // "1,2,3"
由于valueOf
返回的是数组a
自己,仍是对象类型,因此会继续调用toString
方法,返回了字符串"1,2,3"
,因此
'' + [1, 2, 3] // => '' + '1,2,3' => '1,2,3'
那么,若是咱们覆盖数组原型上的valueOf
方法,使得该方法返回一个原始数据类型,那么结果会是什么呢?
var a = [1, 2, 3] a.valueOf = function () { console.log('trigger valueOf') return 'hello' } '' + a // => '' + 'hello' => 'hello'
覆盖默认的valueOf
以后,调用valueOf
会返回原始数据类型。根据OrdinaryToPrimitive
的3.2.2
,这个时候就直接返回了,不会再调用toString
方法。同时在控制台会log
出"trigger valueOf"
,也就是说valueOf
确实是调用了。
那么,若是咱们覆盖数组默认的toString
方法,使得该方法返回对象类型,那么结果会是什么呢?
var a = [1, 2, 3] a.toString = function () { console.log('trigger toString') return this } '' + a // Uncaught TypeError: Cannot convert object to primitive value
由于数组原型上的valueOf
方法返回对象类型,在上面的例子中,咱们把toString
覆盖了,使它也返回对象类型,那么就会直接走到OrdinaryToPrimitive
的第4
步,也就是抛出类型错误的异常,不能把对象转化为原始数据类型。
在上面咱们提到过能够经过@@toPrimitive
方法来自定义ToPrimitive
的行为,好比下面的例子:
var a = [1, 2, 3] a[Symbol.toPrimitive] = function () { return 'custom' } '' + a // => '' + 'custom' => 'custom'
相加操做在调用ToPrimitive
的时候没有提供PreferredType
,接下来说一个会优先使用hint String
做为PreferredType
的例子:
var a = [1, 2, 3] a.valueOf = function () { console.log('trigger valueOf') return 'hello' } a.valueOf() // "hello" a.toString() // "1,2,3" var obj = {} obj[a] = 'hello' // obj是{1,2,3: "hello"}
在把变量做为键值使用的时候,会调用ToPrimitive
把键值转化为原始数据类型,而且PreferredType
的值是hint String
。经过上面的例子也能够看出来,a.valueOf
和a.toString
的结果都是字符串,可是使用了'1,2,3'
,也就是使用了a.toString
的结果。固然,若是咱们从新定义toString
方法,而且返回对象,那么就会使用valueOf
的值了:
var a = [1, 2, 3] a.valueOf = function () { console.log('trigger valueOf') return 'hello' } a.toString = function () { console.log('trigger toString') return this } var obj = {} obj[a] = 'hello' // obj是{hello: "hello"}
而且会在控制台先log
出"trigger toString"
,后log
出"trigger valueOf"
。固然,若是这两个都返回对象,那么仍是会报错:
var a = [1, 2, 3] // 使用原型链上的valueOf方法 a.toString = function () { console.log('trigger toString') return this } var obj = {} obj[a] = 'hello' // Uncaught TypeError: Cannot convert object to primitive value
在上面讲ToPrimitive
的时候,提到Date
对象和Symbol
对象在原型上定义了@@toPrimitive
方法。在ToPrimitive
的第6
步的操做中,咱们能够看到当没有提供PreferredType
的时候,优先调用valueOf
方法。Date原型上的@@toPrimitive作的事情很是简单:当没有提供PreferredType
的时候,优先调用toString
方法。因此对于上面的操做,Date
对象的行为是不同的:
var a = [1, 2, 3] a.valueOf = function () { return 'hello' } a.valueOf() // "hello" a.toString() // "1,2,3" '' + a // "hello" var date = new Date() date.valueOf() // 1536416960724 date.toString() // "Sat Sep 08 2018 22:29:20 GMT+0800 (中国标准时间)" '' + date // "Sat Sep 08 2018 22:29:20 GMT+0800 (中国标准时间)"
咱们能够看到date
的valueOf
方法和toString
方法都返回原始数据类型,可是优先使用了toString
方法。
本文主要讲解了ToPrimitive
抽象操做,以及一些相关的例子,但愿你们能有所收获。若是本文有什么错误或者不严谨的地方,欢迎在评论区留言。