Symbol和Symbol属性
Symbol
是ES6引入的第6种原始类型数据。ES6之前,属性名都是字符串类型,Symbol
可以为属性添加非字符串名称。
创建
所有原始值,除了Symbol
以外都有各自的字面量形式。通过全局的Symbol
函数来创建一个Symbol
,Symbol
函数接受一个可选参数,用来描述即将创建的Symbol
,这段描述不可用于属性访问。
不能通过
new
调用Symbol
函数,这样会导致程序错误。
ES6扩展了typeof
操作符,支持检测Symbol
类型。Symbol
的描述被存储在内部的[[Description]]
属性中,只有调用Symbol
的toString
方法时才可以读取这个属性。执行console.log
时,隐式调用了firstName
的toString
方法。
使用
所有使用可计算属性名的地方,都可以使用Symbol
。
Symbol共享体系
有时可能需要在不同的代码中共享同一个Symbol
,使用Symbol.for()
方法,创建一个共享的Symbol
。该方法接受一个参数,也就是即将创建的Symbol
的字符串标识符,这个参数也作为Symbol
的描述。
Symbol.for()
方法首先在全局Symbol
注册表中搜索键为uid
的Symbol
是否存在,如果存在,返回已有的Symbol
。否则,创建一个新的Symbol
,并使用这个键在Symbol
全局注册表中注册,随即返回新的Symbol
。
可以使用Symbol.keyFor()
方法在Symbol
全局注册表中检索与Symbol
有关的键。
uid
和uid2
都返回"uid"
这个键,而在全局注册表中不存在uid3
这个Symbol
,也就不存在与之有关的键,所以返回undefined
。
Symbol与类型强制转换
JavaScript中,某些情况下,会发生自动转换类型行为。然而,其他类型没有与Symbol
逻辑等价的值,尤其是不能将Symbol
强制转换成字符串和布尔值
String()
函数调用了uid.toString
方法,返回字符串类型的Symbol
描述里的内容。但是将Symbol
强制转换成字符串或数字类型,会抛出错误。
Symbol属性检索
Object.keys()
和Object.getOwnPropertyNames()
方法可以检索对象中的属性名,前一个返回所有可枚举属性,后一个不考虑是否可枚举,一律返回。这两个方法都不支持Symbol
属性。ES6中新增一个Object.getOwnPropertySymbols()
方法检索对象中的Symbol
属性,该方法返回一个包含所有Symbol
自有属性(非继承)的数组。
通过well-known Symbol暴露内部操作
ES6开放了以前JavaScript中常用的内部操作,并通过预定义一些well-known Symbol来表示。每一个这类Symbol
都是Symbol
对象的一个属性。这些well-known Symbol包括
Symbol.hasInstance
一个在执行instanceof
时调用的内部方法,用于检测对象的继承信息。Symbol.isConcatSpreadable
一个布尔值,用于表示当传递一个集合作为Array.prototype.concat()
方法的参数时,是否应该将集合内的元素归整到同一层级。Symbol.iterator
一个返回迭代器方法Symbol.match
、Symbol.replace
、Symbol.search
、Symbol.split
分别在调用String.prototype.match()
、String.prototype.replace()
、String.prototype.search()
、String.prototype.split()
方法时调用的方法Symbol.species
用于创建派生类的构造函数。Symbol.toPrimitive
一个返回对象原始值的方法Symbol.toStringTag
一个在调用Object.prototype.toString()
方法时使用的字符串,用于创建对象描述。Symbol.unscopables
一个定义了一些不可被with
语句引用的对象属性名称的对象集合。
Symbol.hasInstance
每一个函数都有一个Symbol.hasInstance
方法,用于确定对象是否是函数的实例。该方法在Function.prototye
中定义,所有函数都继承了instanceof
属性的默认行为。该方法不可写,不可配置并且不可枚举。
Symbol.hasInstance
方法只接受一个参数,即要检查的值。如果传入的值是函数的实例,则返回true
。
本质上,ES6只是将instanceof
操作符重新定义为此方法的简写语法。现在引入调用后,就可以随意改变instanceof
的运行方式了。
obj
是myObject
的实例,因为给myObject
改写Symbol.hasInstance
,为其定义一个总是返回false
的新函数。
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable
属性是一个布尔值,如果该属性值为true
,则表示对象有length
属性和数字键,它的数值型属性值应该被独立添加到concat()
调用结果中。它与其他well-known Symbol不同的是,这个Symbol
属性默认情况下不会出现在标准对象中,它是一个可选属性,用于增强作用于特定对象类型的concat()
方法的功能,有效简化其默认特性。
也可以在派生数组子类中将
Symbol.isConcatSpreadable
设置为false
,从而防止元素在调用concat()
方法时被分解。
Symbol.match、Symbol.replace、Symbol.search、Symbol.split属性
字符串方法match
、replace
、search
、split
可以接受正则表达式作为参数,在ES6之前,无法使用开发者自定义的对象来替代正则表达式进行字符串匹配。在ES6中,定义了与上述方法对应的Symbol
,这4个Symbol
属性表示对应字符串方法的第一个参数应该调用的正则表达式参数的方法,它们定义在RegExp.prototype
中,是字符串方法应该使用的默认是想。
Symbol.match
接受一个字符串类型的参数,如果匹配成功,返回匹配元素的数组,否则返回null
Symbol.replace
接受一个字符串类型的参数和一个替换用的字符串,最终返回一个字符串Symbol.search
接受一个字符串参数,如果匹配到内容,则返回数字类型的索引位置,否则返回-1Symbol.split
接受一个字符串参数,根据匹配内容将字符串分解,并返回一个包含分解后片段的数组
如果在对象中定义这些属性,即使不使用正则表达式和以正则表达式为参的方法也可以在对象中实现模式匹配。
尽管hasLengthOf10
不是正则表达式,但给它添加了相应的Symbol
属性,所以传递给字符串方法,可以正常运行。
Symbol.toPrimitive方法
再JavaScript引擎中,当执行特定操作时,会尝试将对象转换到相应的原始值。到底使用哪一个原始值以前是由内部操作决定的。在ES6中,通过Symbol.toPrimitive
方法可以更改返回的原始值。
Symbol.toPrimitive
方法被定义在每一个标准类型的原型上,并且规定了当对象被转换为原始值应当执行的操作。当执行原始值转换时,总是会调用Symbol.toPrimitive
方法并传入一个值做为参数,这个值在规范中被称作类型提示(hint
)。该值有3个选择:"number"
、"string"
、"default"
,对应这些参数,Symbol.toPrimitive
返回的分别是:数字、字符串、无类型偏好的值。
当Symbol.toPrimitive
方法参数传入的为"number"
,大多数标准对象,有以下特性。
- 调用
valueOf
方法,如果结果是原始值,则返回 - 否则,调用
toString
方法,如果结果是原始值,则返回 - 如果无再可选值,则抛出错误
当Symbol.toPrimitive
方法参数传入的为"string"
,大多数标准对象,有以下特性。
- 调用
toString
方法,如果结果是原始值,则返回 - 否则,调用
valueOf
方法,如果结果是原始值,则返回 - 如果无再可选值,则抛出错误
大多数情况下,标准对象会将默认模式按数字("number"
)模式处理(Date
对象,默认模式是字符模式)。可以自定义Symbol.toPrimitive
方法,覆盖这些默认的强制转换特性。
默认模式只用于
==
、+
运算以及Date
构造函数传递一个参数时。
|
|
这里在构造函数Temperature.prototype
上定义了Symbol.toPrimitive
屏蔽了继承的Symbol.toPrimitive
方法。新的方法根据hint
指定的模式返回不同的值(hint
有JavaScript引擎传入)。
Symbol.toStringTag
ES6中,通过Symbol.toStringTag
改变调用Object.prototype.toString
时返回的身份标识。这个Symbol
所代表的属性在每一个对象中都存在,其定义了调用对象的Object.prototype.toString.call()
方法时返回的值。
Person.prototype
继承了Object.prototype.toString()
方法,所以调用me.toString()
方法时也使用了Symbol.toStringTag
的返回值。
Symbol.unscopables
with
语句设计的初衷是免于编写重复代码,但由此会带来,代码可读性变差,执行性能差且容易导致程序出错。最终标准规定,严格模式下,不可以使用with
语句。且这条限制同样影响到了类和模块,默认使用严格模式且没有任何退出的方法。
在ES6环境中,with
语句引用的values
不是with
语句外的变量values
,而是数组本身的values
方法,这样就脱离了代码原本的目标。(测试还是使用with
语句外的变量,Node v10.0.0)。
ES6增加Symbol.unscopables
,这个Symbol
通常用于Array.prototype
,已在with
语句中标示出不创建绑定的属性名。Symbol.unscopables
是以对象的形式出现,它的键是在with
语句中要忽略的标识符,对应的值必须为true
。
这里在Array.prototype
的Symbol.unscopables
定义了ES6新的数组方法,这样在with
语句中不再创建这些方法的绑定。