在JS已有的基本类型(字符串、数值、布尔类型、null与undefined)之外,ES6引入了一种新的基本类型:符号(Symbol)。符号起初被设计用于创建对象私有成员,而这也是JS开发者期待已久的特性。在符号诞生之前,嫁给你字符串作为属性名称导致属性可以被轻易访问。而“私有名称”意味着开发者可以创建非字符串类型的属性名称,由此可以防止使用常规手段来探查这些名称。
创建符号值
符号没有字面量形式,这在JS的基本类型中是独一无二的,有别于布尔类型的true或数值类型的42 等等。你可以使用全局Symbol 函数来创建一个符号值,如下所示:
let firstName = Symbol();
let person = {};
person[firstName] = "Nicholas";
console.log(person[firstName]); // "Nicholas"
同时Symbol 函数还可以接受一个额外的参数用于描述符号值,该描述并不能用来访问对应属性,但它能用于调试,例如:
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)"
识别符号值 由于Symbol是基本类型的值,因此可以使用typeof 运算符来判断一个变量是否为符号。例如:
let symbol = Symbol("test symbol");
console.log(typeof symbol); // "symbol"
共享符号值
ES6提供了“全局符号注册表”可以跨越文件或代码来追踪符号值。创建共享符号值的方法是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() 方法在全局符号注册表中根据符号值检索出对应的键值,例如:
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
符号值的转换
类型转换是JS语言重要的一部分,能够非常灵活地将一种数据类型转换为另一种。然而符号类型在进行转换时非常不灵活,因为其他类型缺乏与符号值的合理等价,尤其是符号值无法被转换为字符串值或数值。因此将符号作为属性所达到的效果时其他类型所无法替代的。
前面的例子使用了 console.log() 来展示符号值的输出,能这么做是由于自动调用了符号的 String() 方法来产生输出。你也可以直接调用 String() 方法来获取相同结果,例如:
let uid = Symbol.for("uid"),
desc = String(uid);
console.log(desc); // "Symbol(uid)"
String() 方法调用了 uid.toString() 来获取符号的字符串描述信息。但若你想直接将符号 转换为字符串,则会引发错误:
let uid = Symbol.for("uid"),
desc = uid + ""; // 引发错误!
将 uid 与空字符串相连接,会首先要求把 uid 转换为一个字符串,而这会引发错误,从而阻止了转换行为。 相似地,你不能将符号转换为数值,对符号使用所有数学运算符都会引发错误。
检索符号属性
Object.keys() 与Object.getOwnPropertyNames() 方法可以检索对象的所有属性名称,前者返回所有的可枚举属性名称,而后者则返回所有属性名称而无视其是否可枚举。然而两者都不能返回符号类型的属性,以保持它们在ES5中的功能不发生变化。而ES6新增了Object.getOwnPropertySymbols() 方法,以便让你可以检索对象的符号类型属性。
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"
使用知名符号暴露内部方法
ES6定义了一些“知名符号”来代表JS中一些公共行为,而这些行为此前被认为只能是内部操作。每一个知名符号都对应全局Symbol 对象的一个属性,例如Symbol.create 。
这些知名符号是:
- 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 语句中。