六: ES6 符号 Symbol
前言
该部分为书籍 深入理解ES6 第六章(符号与符号属性)笔记
创建符号值
- 符号没有字面量形式, 这在
JS
的基本类型中是独一无二的. 使用全局Symbol
函数来创建一个符号值 - 由于符号值是基本类型的值, 因此调用
new Symbol()
将会抛出错误. 你可以通过 new
Object(yourSymbol) 来创建一个符号实例,但尚不清楚这能有什么作用。 -
Symbol
函数还可以接受一个额外的参数用于描述符号值, 该描述不能用来访问对应属性, 但它能用于调试. 符号的描述信息被存储在内部属性[[Description]]
中, 当符号的toString()
方法被显式或隐式调用时, 该属性都会被读取. 此外没有任何办法可以从代码中直接访问[[Description]]
属性
let firstName = Symbol("first name");
let person = {};
person[firstName] = "Nicholas";
console.log("first name" in person); // false
console.log(person[firstName]); // "Nicholas"
console.log(firstName); // "Symbol(first name)"
-
识别符号值: 可以使用
typeof
运算符来判断一个变量是否为符号let symbol = Symbol("test symbol"); console.log(typeof symbol); // "symbol"
尽管有其他方法可以判断一个变量是否为符号, typeof 运算符依然是最准确、最优先
的判别手段。
使用符号值
可以在任意能使用 "需计算属性名" 的场合使用符号. 还可以在 Object.defineProperty()
或 Object.defineProperties()
调用中使用它
let firstName = Symbol("first name");
// 使用一个需计算字面量属性
let person = {
[firstName]: "Nicholas"
};
// 让该属性变为只读
Object.defineProperty(person, firstName, { writable: false });
let lastName = Symbol("last name");
Object.defineProperties(person, {
[lastName]: {
value: "Zakas",
writable: false
}
});
console.log(person[firstName]); // "Nicholas"
console.log(person[lastName]); // "Zakas"
共享符号值
-
创建共享符号值, 应使用
Symbol.for()
方法而不是Symbol()
方法.Symbol.for()
方法仅接受单个字符串类型的参数,作为目标符号值的标识符,同时此参数也会成为该符号的描述信息。let uid = Symbol.for("uid"); let object = {}; object[uid] = "12345"; console.log(object[uid]); // "12345" console.log(uid); // "Symbol(uid)"
-
Symbol.for()
方法首先会搜索全局符号注册表,看是否存在一个键值为 "uid" 的符号值。若是,该方法会返回这个已存在的符号值;否则,会创建一个新的符号值,并使用该键值将其记录到全局符号注册表中,然后返回这个新的符号值 let uid = Symbol.for("uid"); let object = { [uid]: "12345" }; console.log(object[uid]); // "12345" console.log(uid); // "Symbol(uid)" let uid2 = Symbol.for("uid"); console.log(uid === uid2); // true console.log(object[uid2]); // "12345" console.log(uid2); // "Symbol(uid)"
-
可以使用
Symbol.keyFor()
方法在全局符号注册表中根据符号值检索出对应的键值,注意: 使用
Symobol()
方法创建的是不会注册到全局符号注册表的let uid = Symbol.for("uid"); console.log(Symbol.keyFor(uid)); // "uid" let uid2 = Symbol.for("uid"); console.log(Symbol.keyFor(uid2)); // "uid" let uid3 = Symbol("uid"); console.log(Symbol.keyFor(uid3)); // undefined
-
全局符号注册表类似于全局作用域,是一个共享环境,这意味着你不应当假设某些值是否已存在于其中。在使用第三方组件时,为符号的键值使用命名空间能够减少命名冲突的可能性,举个例子: jQuery 代码应当为它的所有键值使用 "jquery." 的前缀,如"jquery.element" 或类似的形式。
符号值的转换
- 符号类型在进行转换时非常不灵活, 因为其他类型缺乏与符号值的合理等价, 尤其是符号值无法被转换为字符串值或数值(会抛出错误)
- 无论对符号使用哪种数学运算符都会导致错误,但使用逻辑运算符则不会,因为符号值在逻辑运算中会被认为等价于 true (就像 JS中其他的非空值那样)。
检索符号属性
ES6 新增了Object.getOwnPropertySymbols() 方法,以便让你可以检索对象的符号类型属性。
let uid = Symbol.for("uid");
let object = {
[uid]: "12345"
};
let symbols = Object.getOwnPropertySymbols(object);
console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(uid)"
console.log(object[symbols[0]]); // "12345"
使用知名符号暴露内部方法
ES5 的中心主题之一是披露并定义了一些魔术般的成分,而这些部分是当时开发者所无法自行模拟的。 ES6 延续了这些工作,对原先属于语言内部逻辑的部分进行了进一步的暴露,允许使用符号类型的原型属性来定义某些对象的基础行为。
**这些符号是: **
- 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.split() 函数使用的一个方法,用于分割字符串。
- Symbol.toPrimitive :返回对象所对应的基本类型值的一个方法。
- Symbol.toStringTag :供 String.prototype.toString() 函数使用的一个方法,用于创建对象的描述信息。
- Symbol.unscopables :一个对象,该对象的属性指示了哪些属性名不允许被包含在with 语句中。
重写知名符号所定义的方法, 会把一个普通对象改变成奇异对象, 因为它改变了一些默认的内部行为
1. Symbol.hasInstance 属性
-
作用: 用于判断指定对象是否为本函数的一个实例。 方法定义在
Function.prototype
上, 因此所有函数都继承了面对instanceof
运算符时的默认行为obj instanceof Array; // 等价于 -- ES6 从本质上将 instanceof 运算符重定义为上述方法调用的简写语法,这样使用 instanceof 便会触发一次方法调用,实际上允许你改变该运算符的工作。 Array[Symbol.hasInstance](obj);
-
Symbol.hasInstance
属性自身是不可写入、不可配置、不可枚举的,从而保证它不会被错误地重写 -
要重写一个不可写入的属性, 必须使用
Object.defineProperty()
function SpecialNumber() { // empty } // 通过重写构造函数本身的属性来重写 instanceof 运算符的结果 Object.defineProperty(SpecialNumber, Symbol.hasInstance, { value: function(v) { // 将介于 1 到 100 之间的数值认定为一个特殊的数值类型, return (v instanceof Number) && (v >=1 && v <= 100); } }); let two = new Number(2), zero = new Number(0); console.log(two instanceof SpecialNumber); // true console.log(zero instanceof SpecialNumber); // false
-
可以重写所有内置函数(例如 Date 或 Error )的 Symbol.hasInstance 属性,但并不建议这么做,因为这会让你的代码变得难以预测而又混乱。最好仅在必要时对自己的函数重写Symbol.hasInstance 。
2. Symbol.isConcatSpreadable
- 作用: Symbol.isConcatSpreadable 属性是一个布尔类型的属性,它表示目标对象拥有长度属性与数值类型的键、并且数值类型键所对应的属性值在参与 concat() 调用时需要被分离为个体。
3. Symbol.match、 Symbol.replace、Symbol.search、Symobol.split
- 作用: 这 4 个符号表示可以将正则表达式作为字符串对应方法的第一个参数传入, Symbol.match对应 match() 方法, Symbol.replace 对应 replace() , Symbol.search 对应 search(), Symbol.split 则对应 split() 。这些符号属性被定义在 RegExp.prototype 上作为默认实现,以供对应的字符串方法使用。
4. Symbol.toPrimittive
-
作用: Symbol.toPrimitive 方法被定义在所有常规类型的原型上,规定了在对象被转换为基本类型值的时候会发生什么。当需要转换时, Symbol.toPrimitive 会被调用,并按照规范传入一个提示性字符串参数。该参数有 3 种可能:当参数值为 "number" 的时候,Symbol.toPrimitive 应当返回一个数值;当参数值为 "string" 的时候,应当返回一个字符串;而当参数为 "default" 的时候,对返回值类型没有特别要求。
通过定义 Symbol.toPrimitive 方法,你可以重写这些默认的转换行为。
对于大部分常规对象,“数值模式”依次会有下述行为:
- 调用 valueOf() 方法,如果方法返回值是一个基本类型值,那么返回它;
- 否则,调用 toString() 方法,如果方法返回值是一个基本类型值,那么返回它;
- 否则,抛出一个错误。
类似的,对于大部分常规对象,“字符串模式”依次会有下述行为:
-
调用 toString() 方法,如果方法返回值是一个基本类型值,那么返回它;
-
否则,调用 valueOf() 方法,如果方法返回值是一个基本类型值,那么返回它;
-
否则,抛出一个错误
在多数情况下,常规对象的默认模式都等价于数值模式(只有 Date 类型例外,它默认使用
字符串模式) ==> “默认模式”只在使用 == 运算符、 + 运算符、或者传递单一参数给 Date 构造器的时候被使用,而大部分运算符都使用字符串模式或是数值模式
function Temperature(degrees) {
this.degrees = degrees;
}
Temperature.prototype[Symbol.toPrimitive] = function(hint) {
switch (hint) {
case "string":
return this.degrees + "\u00b0"; // 温度符号
case "number":
return this.degrees;
case "default":
return this.degrees + " degrees";
}
};
let freezing = new Temperature(32);
console.log(freezing + "!"); // "32 degrees!"
console.log(freezing / 2); // 16
console.log(String(freezing)); // "32°"
5. Symbol.toStringTag
- 作用: ES6 通过 Symbol.toStringTag 重定义了相关行为,该符号代表了所有对象的一个属性,定义了 Object.prototype.toString.call() 被调用时应当返回什么值。也就是修改对象内部属性[[class]]
function Person(name) {
this.name = name;
}
Person.prototype[Symbol.toStringTag] = "Person";
let me = new Person("Nicholas");
console.log(me.toString()); // "[object Person]"
console.log(Object.prototype.toString.call(me)); // "[object Person]"
6. Symbol.unscopables
- 作用: 在 Array.prototype 上使用,以指定哪些属性不允许在 with 语句内被绑定。