1.sysmbol类型
根据规范,只有两种原始类型可以用作对象属性键:
- 字符串类型
- symbol 类型
“symbol” 值表示唯一的标识符。
可以使用 Symbol() 来创建这种类型的值:
let id = Symbol();
创建时,我们可以给 symbol 一个描述(也称为 symbol 名),这在代码调试时非常有用:
// id 是描述为 "id" 的 symbol let id = Symbol("id");
想显示一个 symbol,我们需要在它上面调用 .toString(),如下所示:
let id = Symbol("id");
alert(id.toString()); // Symbol(id),现在它有效了
或者获取 symbol.description 属性,只显示描述(description):
let id = Symbol("id");
alert(id.description); // id
“隐藏”属性
symbol 允许创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。
例如,如果使用的是属于第三方代码的 user 对象,想要给它们添加一些标识符。
可以给它们使用 symbol 键:
let user = { // 属于另一个代码
name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // 我们可以使用 symbol 作为键来访问数据
使用 Symbol("id") 作为键,比起用字符串 "id" 来有什么好处呢?
由于 user 对象属于另一个代码库,所以向它们添加字段是不安全的,因为我们可能会影响代码库中的其他预定义行为。但 symbol 属性不会被意外访问到。第三方代码不会知道新定义的 symbol,因此将 symbol 添加到 user 对象是安全的。
另外,假设另一个脚本希望在 user 中有自己的标识符,以实现自己的目的。
那么,该脚本可以创建自己的 Symbol("id"),像这样:
// ... let id = Symbol("id"); user[id] = "Their id value";
我们的标识符和它们的标识符之间不会有冲突,因为 symbol 总是不同的,即使它们有相同的名字。
……但如果我们处于同样的目的,使用字符串 "id" 而不是用 symbol,那么 就会 出现冲突:
let user = { name: "John" };
// 我们的脚本使用了 "id" 属性。
user.id = "Our id value";
// ……另一个脚本也想将 "id" 用于它的目的……
user.id = "Their id value"
// 砰!无意中被另一个脚本重写了 id!
对象字面量中的 symbol
如果我们要在对象字面量 {...} 中使用 symbol,则需要使用方括号把它括起来。
就像这样:
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // 而不是 "id":123
};
symbol 在 for…in 中会被跳过
symbol 属性不参与 for..in 循环。
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age(没有 symbol)
// 使用 symbol 任务直接访问
alert("Direct: " + user[id]); // Direct: 123
Object.keys(user) 也会忽略它们。这是一般“隐藏符号属性”原则的一部分。如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。
相反,Object.assign 会同时复制字符串和 symbol 属性:
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = Object.assign({}, user);
alert( clone[id] ); // 123
总结
symbol 是唯一标识符的基本类型
symbol 是使用带有可选描述(name)的 Symbol() 调用创建的。
symbol 总是不同的值,即使它们有相同的名字。如果我们希望同名的 symbol 相等,那么我们应该使用全局注册表:Symbol.for(key) 返回(如果需要的话则创建)一个以 key 作为名字的全局 symbol。使用 Symbol.for 多次调用 key 相同的 symbol 时,返回的就是同一个 symbol。
symbol 有两个主要的使用场景:
-
“隐藏” 对象属性。
如果我们想要向“属于”另一个脚本或者库的对象添加一个属性,我们可以创建一个 symbol 并使用它作为属性的键。symbol 属性不会出现在
for..in中,因此它不会意外地被与其他属性一起处理。并且,它不会被直接访问,因为另一个脚本没有我们的 symbol。因此,该属性将受到保护,防止被意外使用或重写。因此我们可以使用 symbol 属性“秘密地”将一些东西隐藏到我们需要的对象中,但其他地方看不到它。
-
JavaScript 使用了许多系统 symbol,这些 symbol 可以作为
Symbol.*访问。我们可以使用它们来改变一些内建行为。例如,在本教程的后面部分,我们将使用Symbol.iterator来进行 迭代 操作,使用Symbol.toPrimitive来设置 对象原始值的转换 等等。
从技术上说,symbol 不是 100% 隐藏的。有一个内建方法 Object.getOwnPropertySymbols(obj) 允许我们获取所有的 symbol。还有一个名为 Reflect.ownKeys(obj) 的方法可以返回一个对象的 所有 键,包括 symbol。但大多数库、内建方法和语法结构都没有使用这些方法。
浙公网安备 33010602011771号