6.Symbol和Symbol属性
Symbol是JavaScript中一个新的原始类型,用于创建必须通过Symbol才能使用的属性。尽管这些属性不是完全私有的,但是他们比较难以被意外覆盖而改变。表示独一无二的值。可防止属性名冲突。
6.1 创建Symbol
所有原始类型值,除Symbol以外都有各自的字面量形式。可以通过全局的Symbol函数创建一个Symbol:1
2
3
4let firstName = Symbol();
let person = {};
person[firstName] = “Nicholas”;
console.log(person[firstName]); //”Nicholas”
在上面这段代码中,创建了一个名为firstName的Symbol,用它将一个新的属性赋值给person对象,每当你想访问这个属性时一定要用到最初定义的Symbol。记得要合理命名Symbol变量,这样可以轻松区分它所指代的内容。
注意,创建Symbol不要使用new调用。
Symbol函数接受一个可选参数,其可以让你添加一段文本描述即将常见的Symbol,这段描述不可用于属性访问,只能方便阅读和调试。描述被存储在内部的[[Description]]属性中,只有当调用Symbol的toString()方法可以读取该属性。
Symbol使用typeof辨识,返回”symbol”。
6.2 Symbol的使用方法
Symbol可用于可计算对象字面量属性名、Object.defineProperty()方法和Object.defineProperties()方法的调用过程中。
6.3 Symbol共享体系
有时我们可能希望在不同的代码中共享同一个Symbol,例如,在你的应用中有种不同的对象类型,但是你希望它们使用同一个Symbol属性来表示一个独特的标识符。一般而言,在很大的代码库中或跨文件追踪Symbol非常困难而且容易出错,处于这些原因,ES6提供了一个可以随时访问的全局Symbol注册表。
如果想创建一个可共享的Symbol,要使用Symbol.for()方法。传入即将要创建的Symbol的字符串标识符(键-作为Symbol唯一的标识),同时作为Symbol的描述。1
let uid = Symbol.for(“uid”);
该方法首先在全局Symbol注册表中搜索键为”uid”的Symbol是否存在,存在后直接返回已有的Symbol,不存在则创建一个新的Symbol并返回。
Symbol.ketFor()方法:在Symbol全局注册表中检索与Symbol有关的键,返回创建时传入的标识符(键)。不存在时返回undefined。
6.4 Symbol与类型强制转换
console.log()输出Symbol时,会调用Symbol的String()方法,String()会调用Symbol的toString()方法,返回字符串类型的Symbol描述里的内容。1
2let uid = Symbol.for(“uid”);
console.log(uid); //”Symbol(uid)”
但是不能将Symbol与一个字符串拼接,也不能进行算术运算,也就是Symbol无法强制转换为数值与字符串。
6.5 Symbol属性检索
Object.keys()—返回所有可枚举的属性名;
Object.getOwnPropertyNames()—不考虑属性的可枚举性一律返回;
上面两个方法都不支持Symbol属性。
Object.getOwnPropertySymbols()—检索对象中的所有Symbol属性,返回一个包含所有Symbol自有属性的数组。
6.6 通过well-known Symbol暴露内部操作
ES5的一个中心主旨是将JavaScript中的一些“神奇”的部分暴露出来,并详尽定义了这些开发者们在当时模拟不了的功能。ES6延续了这个传统,新标准中主要通过在原型链上定义与 Symbol相关的属性来暴露更多语言内部逻辑。
ES6开放了以前JavaScript中常见的内部操作,并通过预定义一些well-known Symbol来表示。每一个这类Symbol都是Symbol对象的一个属性。这些well-known Symbol包括:
- Symbol.hasInstance:一个在执行instanceof时调用的内部方法,用于检测对象的继承信息。
- Symbol.isConcatSpreadable:一个布尔值,用于当传递一个集合作为Array.prototype.concat()方法的参数时,是否应该将集合内的元素规整到同一层级。
- Symbol.iterator:一个返回迭代器的方法
- Symbol.match:一个在调用String.prototype.match()方法时调用的方法,用于比较字符串。
- Symbol.replace:一个在调用String.prototype.replace()方法时调用的方法,用于替换字符串的子串。
- Symbol.search:一个在调用String.prototype.search()方法时调用的方法,用于在字符串中定位子串。
- Symbol.species:用于创建派生类的构造函数。
- Symbol.split:一个在调用String.prototype.toString()方法时使用的字符串,用于分割字符串
- Symbol.toPrimitive:一个返回对象原始值的方法。
- Symbol.toStringTag:一个在调用Object.prototype.toString()方法时使用的字符串,用于创建对象描述。
- Symbol.unscopables:一个定义了一些不可被with语句引用的对象属性名称的对象集合。
重写一个由well-known Symbol定义的方法,会导致对象内部的默认行为被改变,从而一个普通对象会变成一个奇异对象。但实际上其不会对你的代码产生任何影响,只是在规范中描述对象的方式改变了。Symbol.hasInstance方法
该方法用于确定对象是否为函数的实例。该方法在Function.prototype中定义,所以所有函数都继承了instanceof属性的默认行为。该方法被定义为不可写,不可配置并且不可枚举。
Symbol.hasInstance方法只接受一个参数,即要检查的值。如果传入的是函数的实例,则返回true。obj instanceof Array;
等价于Array[Symbol.hasInstance](obj);
本质上ES6只是将instanceof操作符重新定义为吃方法的简写语法。使用 Object.defineProperty()方法可改写一个不可写属性,若利用它改写了Symbol.hasInstance方法后,instanceof操作符运算返回的结果也会改变。
最好不要重写内建函数默认的Symbol.hasInstance属性。最好的做法是:只在必要情况下改写你自己声明的函数的Symbol.hasInstance属性Symbol.isConcatSpreadable方法
concat()方法传入数组参数时,会自动将其分解为独立元素。现在可通过Symbol.isConcatSpreadable属性修改这个特性,该属性为一个布尔值,如果该属性为true,表示对象有length属性和数字键,故它的数值型属性值应该被独立添加到concat()调用的结果中。
与其他well-known Symbol不同,这个Symbol属性默认情况下不会出现在标准对象中,它只是一个可选属性,用于增强作用于特定对象类型的concat()方法中的功能,有效简化其默认特性。
也可以在派生数组子类中将Symbol.isConcatSpreadable设置为false,从未防止元素在调用concat()方法时被分解。Symbol.match、Symbol.replace、Symbol.rearch和Symbol.split属性
字符串的match(regex),replace(regex,replacement),search(regex)和split(regex)这个四个方法无法使用开发者滴定仪的对象来替代正则表达式进行字符串匹配。
在ES6中,定义了上述4个方法对应的4个Symbol,将语言内建的RexExp对象的原生特性完全外包出来。
Symbol.match、Symbol.replace、Symbol.rearch和Symbol.split这4个属性表示match()、replace()、search()和split()方法中的第一个参数应该调用的正则表达式参数的方法,它们被定义在RegExp.prototype中,是字符串方法应该使用的默认实现。
我们可以类似于正则表达式的方法创建一个与字符串方法一起使用的对象,然后在上述4个方法中传入该对象,该对象的4个属性为: - Symbol.match:接受一个字符串类型的参数,如果匹配成功则返回匹配元素的数组,否则返回null
- Symbol.replace:接受一个字符串类型的参数和一个替换用的字符串,最终仍然返回一个字符串
- Symbol.rearch:接受一个字符串参数,如果匹配到内容,则返回数字的索引位置,否则返回-1
- Symbol.split:接受一个字符串参数,根据匹配内容将字符串分解,并返回一个包含分解后片段的数组
如果可以在对象定义这些属性,可用该对象代替正则表达式,即使不使用正则表达式和以正则表达式为参的方法也可以在对象中实现模式匹配。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30//实际上等价于/^.{10}$/
let hasLengthOf10 = {
[Symbol.match]: function(value) {
return value.length === 10 ? [value.substring(0,10)] : null;
},
[Symbol.replace]: function(value, replacement) {
return value.length === 10 ? replacement+value.substring(10) : value;
},
[Symbol.search]: function(value) {
return value.length === 10 ? 0 : -1;
},
[Symbol.split]: function(value) {
return value.length === 10 ? [“”, “”] : [value];
},
};
let message1 = “Hello World”,
message2 = ”Hello John”;
let match1 = message1.match(hasLengthOf10), //null
match2 = message2.match(hasLengthOf10); //[“Hello John”]
let replace1 = message1.replace(hasLengthOf10), //”Hello World”
replace2 = message2.replace(hasLengthOf10); //”Hello John”
let search1 = message1.search(hasLengthOf10), //-1
search2 = message1.search(hasLengthOf10); //0
let split1 = message1.split(hasLengthOf10), //[“Hello World”]
split1 = message1.split(hasLengthOf10); //[“”,””]
Symbol.toPrimitive
在JavaScript引擎中,当执行特定操作时,经常会尝试将对象转换到相应的原始值,例如,比较一个字符串和一个对象,如果使用==运算符,对象会在比较操作执行前被转换为一个原始值。到底使用哪个原始值以前是由内部操作决定的,但在ES6中,通过Symbol.toPrimitive方法可以更改哪个暴露出来的值。
Symbol.toPrimitive方法被定义在每一个标准类型的原型上,并且规定了当对象被转换为原始值应执行的操作。每当执行原始值转换时,总会调用Symbol.toPrimitive方法并传入一个值作为参数,这个值在规范中被称作类型提示(hint)。类型提示参数的值只有单中选择:”number”、”string”或”default”,传递这些参数时,Symbol.toPrimitive返回的分别时数字,字符串或者无类型偏好的值。
转换为原始值的顺序:
- 调用valueof()方法,如果结果为原始值,则返回。
- 否则调用toString()方法,如果结果为原始值,则返回。
- 如果再无可选值,则抛出错误。
在大多数情况下,标准对象会将默认模式按数字模式处理(除了Date对象,在这种情况下,会将默认模式按字符串模式处理)。如果自定义Symbol.toPrimitive方法,则可以覆盖这些默认的强制转换特性。
注意:默认模式只用于==,+以及给Date构造函数传递一个参数时。Symbol.toStringTag属性
在一个页面中存在多个全局执行环境时,数据来回传递无法检测数据类型。针对类型识别问题的解决方案
1
2
3function isArray(value){
return Object.prototype.toString.call(value) === “[object Array]”;
}
在ES6中定义对象字符串标签
在ES6中重新定义了原生对象过去的状态,通过Symbol.toStringTag这个Symbol改变了调用Object.prototype.toString()是返回的身份标识。这个Symbol所代表的的属性在每一个对象中都存在,其定义了调用对象的Object.prototype.toString.call()方法时返回的值。对于数组,调用那个函数返回的通常是”Array”,它正式存储在对象的Symbol.toStringTag属性中。
同样,可以为自己定义的对象定义Symbol.toStringTag属性。
注意:除非另有说明,所有对象都会从Symbol.toStringTag这个属性,且默认的属性值”Object”。
禁止修改内建对象,和将自定义对象的该属性的值修改为内建对象的值。