ECMAScript7规范中的ToPrimitive抽象操做

本文将介绍ECMAScript7规范中的ToPrimitive抽象操做。算法

预备知识

ECMAScript数据类型

ECMAScript数据类型细分为两大类数据类型,一种是语言类型,一种是规范类型数组

  • 语言类型是能够直接被开发人员使用的数据类型;
  • 规范类型表明meta-values(元值),用在算法中描述ECMAScript语言结构和语言类型的语义。它们主要用于规范的说明,不须要被真正地实现。

ECMAScript语言类型一共有7种:this

  • Undefined
  • Null
  • Boolean,布尔类型
  • String,字符串类型
  • Symbol,符号类型
  • Number,数字类型
  • Object,对象类型

原始数据类型是上述UndefinedNullBooleanStringSymbolNumber的统称,也就是非对象数据类型。
下文涉及到的规范类型只有List,也就是列表,相似于数组,用符号« »表示。lua

@@toPrimitive

Symbol有不少有名的符号,好比@@toPrimitive,也就是Symbol.toPrimitive,这是定义在Symbol对象上的一个属性。prototype

ToPrimitive(input [, PreferredType])

该抽象操做接受一个参数input和一个可选的参数PreferredType。该抽象操做的目的是把参数input转化为非对象数据类型,也就是原始数据类型。若是input能够同时转化为多个原始数据,那么会优先参考PreferredType的值。转化过程参照下表:code

参数input的数据类型 结果
Undefined 返回input自身
Null 返回input自身
Boolean 返回input自身
Number 返回input自身
String 返回input自身
Symbol 返回input自身
Object 执行下面的步骤

若是input的数据类型是对象,执行下述步骤:对象

  1. 若是没有传入PreferredType参数,让hint等于"default"
  2. 若是PreferredTypehint String,让hint等于"string"
  3. 若是PreferredTypehint Number,让hint等于"number"
  4. exoticToPrim等于GetMethod(input, @@toPrimitive),大概语义就是获取参数input@@toPrimitive方法;
  5. 若是exoticToPrim不是Undefined,那么:ip

    1. result等于Call(exoticToPrim, input, « hint »),大概语义就是执行exoticToPrim(hint)
    2. 若是result是原始数据类型,返回result
    3. 抛出类型错误的异常;
  6. 若是hint"default",让hint等于"number"
  7. 返回OrdinaryToPrimitive(input, hint)抽象操做的结果。

OrdinaryToPrimitive(O, hint)

O的数据类型是对象,hint的数据类型是字符串,而且hint的值要么是"string",要么是"number"。该抽象操做的步骤以下:原型链

  1. 若是hint"string",让methodNames等于« "toString", "valueOf" »
  2. 若是hint"number",让methodNames等于« "valueOf", "toString" »
  3. 按顺序迭代列表methodNames,对于每个迭代值name开发

    1. method等于Get(O, name),大概语义就是获取对象Oname值对应的属性;
    2. 若是method能够调用,那么:

      1. method等于Call(method, O),大概语义就是执行method()
      2. 若是result的类型不是对象,返回result
  4. 抛出类型错误的异常。

由上述操做步骤可知:

  • 经过ToPrimitive的步骤6可知,当没有提供可选参数PreferredType的时候,hint会默认为"number"
  • 经过ToPrimitive的步骤4可知,能够经过定义@@toPrimitive方法来覆盖默认行为,好比规范中定义的Date日期对象和Symbol符号对象都在原型上定义了@@toPrimitive方法。

实践

可能有人会问,为何要讲解规范中的抽象方法,抽象方法我又用不到。其实否则,这个方法在不少地方都会用到,只是你不知道罢了。下面经过讲解几个实例让你们加深对它的理解。

'' + [1, 2, 3]

'' + [1, 2, 3] // "1,2,3"

根据规范中的加法操做,对于操做x + y,会调用ToPrimitive(x)ToPrimitive(y)xy转化为原始数据类型。上面的例子中''自己就是原始数据类型了,因此返回''自身。[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会返回原始数据类型。根据OrdinaryToPrimitive3.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.valueOfa.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

Date

在上面讲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 (中国标准时间)"

咱们能够看到datevalueOf方法和toString方法都返回原始数据类型,可是优先使用了toString方法。

总结

本文主要讲解了ToPrimitive抽象操做,以及一些相关的例子,但愿你们能有所收获。若是本文有什么错误或者不严谨的地方,欢迎在评论区留言。

相关文章
相关标签/搜索