本篇文章给大家带来的内容是关于ecmascript7规范中toprimitive抽象操作的详细解析(示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
本文将介绍ecmascript7规范中的toprimitive抽象操作。
预备知识ecmascript数据类型ecmascript数据类型细分为两大类数据类型,一种是语言类型,一种是规范类型:
语言类型是可以直接被开发人员使用的数据类型;
规范类型代表meta-values(元值),用在算法中描述ecmascript语言结构和语言类型的语义。它们主要用于规范的说明,不需要被真正地实现。
ecmascript的语言类型一共有7种:
undefined
null
boolean,布尔类型
string,字符串类型
symbol,符号类型
number,数字类型
object,对象类型
原始数据类型是上述undefined、null、boolean、string、symbol和number的统称,也就是非对象数据类型。
下文涉及到的规范类型只有list,也就是列表,类似于数组,用符号« »表示。
@@toprimitivesymbol有很多有名的符号,比如@@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的数据类型是对象,执行下述步骤:
1、如果没有传入preferredtype参数,让hint等于default;
2、如果preferredtype是hint string,让hint等于string;
3、如果preferredtype是hint number,让hint等于number;
4、让exotictoprim等于getmethod(input, @@toprimitive),大概语义就是获取参数input的@@toprimitive方法;
5、如果exotictoprim不是undefined,那么:
让result等于call(exotictoprim, input, « hint »),大概语义就是执行exotictoprim(hint);
如果result是原始数据类型,返回result;
抛出类型错误的异常;
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:
让method等于call(method, o),大概语义就是执行method();
如果result的类型不是对象,返回result;
让method等于get(o, name),大概语义就是获取对象o的name值对应的属性;
如果method可以调用,那么:
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)把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() // helloa.tostring() // 1,2,3var 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
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() // helloa.tostring() // 1,2,3'' + a // hellovar date = new date()date.valueof() // 1536416960724date.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抽象操作,以及一些相关的例子,希望大家能有所收获。
以上就是详解ecmascript7规范中toprimitive抽象操作的知识(示例)的详细内容。