资讯专栏INFORMATION COLUMN

ECMAScript7规范中的ToPrimitive抽象操作

张汉庆 / 1030人阅读

摘要:本文将介绍规范中的抽象操作。它们主要用于规范的说明,不需要被真正地实现。该抽象操作接受一个参数和一个可选的参数。根据规范中的加法操作,对于操作,会调用和把和转化为原始数据类型。

本文将介绍ECMAScript7规范中的ToPrimitive抽象操作。

预备知识 ECMAScript数据类型

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

语言类型是可以直接被开发人员使用的数据类型;

规范类型代表meta-values(元值),用在算法中描述ECMAScript语言结构和语言类型的语义。它们主要用于规范的说明,不需要被真正地实现。

ECMAScript语言类型一共有7种:

Undefined

Null

Boolean,布尔类型

String,字符串类型

Symbol,符号类型

Number,数字类型

Object,对象类型

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

@@toPrimitive

Symbol有很多有名的符号,比如@@toPrimitive,也就是Symbol.toPrimitive,这是定义在Symbol对象上的一个属性。

ToPrimitive(input [, PreferredType])

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

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

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

如果没有传入PreferredType参数,让hint等于"default"

如果PreferredTypehint String,让hint等于"string"

如果PreferredTypehint Number,让hint等于"number"

exoticToPrim等于GetMethod(input, @@toPrimitive),大概语义就是获取参数input@@toPrimitive方法;

如果exoticToPrim不是Undefined,那么:

result等于Call(exoticToPrim, input, « hint »),大概语义就是执行exoticToPrim(hint)

如果result是原始数据类型,返回result

抛出类型错误的异常;

如果hint"default",让hint等于"number"

返回OrdinaryToPrimitive(input, hint)抽象操作的结果。

OrdinaryToPrimitive(O, hint)

O的数据类型是对象,hint的数据类型是字符串,并且hint的值要么是"string",要么是"number"。该抽象操作的步骤如下:

如果hint"string",让methodNames等于« "toString", "valueOf" »

如果hint"number",让methodNames等于« "valueOf", "toString" »

按顺序迭代列表methodNames,对于每一个迭代值name

method等于Get(O, name),大概语义就是获取对象Oname值对应的属性;

如果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] // "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抽象操作,以及一些相关的例子,希望大家能有所收获。如果本文有什么错误或者不严谨的地方,欢迎在评论区留言。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97583.html

相关文章

  • JavaScript中的==,===和Object.is()

    概述 本文主要讲解JavaScript中的三种相等运算:==,===和Object.is()。通过对比和例子,加深大家的印象,并就个别例子进行详细说明。 预备知识 ECMAScript7规范中的ToPrimitive抽象操作 ===运算符 对于x === y,该运算符的比较步骤如下: 如果x的类型和y的类型不一样,返回false; 如果x的类型是数字,那么: 如果x是NaN,返回false;...

    hiyang 评论0 收藏0
  • 使用JavaScript隐式类型转换输出"nb"

    摘要:本文将介绍一段使用隐式类型转换输出的代码,并讲解具体的转换过程。代码转换过程我们分四部分讲解具体的转换过程,一个空数组,紧跟在数组后面的的语义应该是表示属性操作,类似于中的作用,而不是表示数组。 本文将介绍一段使用JavaScript隐式类型转换输出nb的代码,并讲解具体的转换过程。 预备知识 请先阅读文章ECMAScript7规范中的ToPrimitive抽象操作。 代码 ([][[...

    tomlingtm 评论0 收藏0
  • ECMAScript7规范中的instanceof操作

    摘要:本文主要讲解规范中的操作符。由上述步骤可知,如果是一个函数,那么会重新在绑定的目标函数上执行操作。而使用的方式的时候,给构造函数添加一个静态方法,相当于给对象赋值,赋值操作会先检查原型链上是否存在同名属性,所以就会有赋值失败的风险。 本文主要讲解ECMAScript7规范中的instanceof操作符。 预备知识 有名的Symbols 有名的Symbols指的是内置的符号,它们定义在S...

    zhangwang 评论0 收藏0
  • 从0开始构建自己的前端知识体系-不要对"=="说不

    摘要:为了避免某些场景下的意外,甚至推崇直接使用来代替。使用了运算符的一些规则,发生了类型转换。按照以下规则转换被传递参数直接返回直接返回直接返回直接返回直接返回返回一个对象的默认值。 前言 类型转换在各个语言中都存在,而在 JavaScript 中由于缺乏对其的了解而不慎在使用中经常造成bug被人诟病。为了避免某些场景下的意外,甚至推崇直接使用 Strict Equality( === )...

    tianyu 评论0 收藏0
  • 怎样阅读 ECMAScript 规范

    摘要:另一方面,我不建议初次接触的开发人员阅读规范。在维护语言的最新规范。在这一点上,我想指出的是,绝对没有人从上到下阅读规范。拓展阅读由于的定义,中的细节如冒泡错误,直到块在规范中不存在。换句话说,会转发中抛出的错误,并终止其余的步骤。 翻译自:How to Read the ECMAScript Specification Ecmascript 语言规范 The ECMAScr...

    lpjustdoit 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<