Symbol 范例
依据范例,对象的属性键只能是 String 范例或许 Symbol 范例。不是 Number,也不是 Boolean,只要 String 或 Symbol 这两种范例。
到目前为止,我们只见过 String。如今我们来看看 Symbol 能给我们带来什么优点。
Symbol
“Symbol” 值示意唯一的标识符。
能够运用 Symbol()
来建立这类范例的值:
// id 是 symbol 的一个实例化对象
let id = Symbol();
我们能够给 Symbol 一个形貌(也称为 Symbol 名),这关于调试异常有效:
// id 是形貌为 "id" 的 Symbol
let id = Symbol("id");
Symbol 保证是唯一的。纵然我们建立了许多具有雷同形貌的 Symbol,它们的值也是差别。形貌只是一个不影响任何东西的标签。
比方,这里有两个形貌雷同的 Symbol —— 它们不相称:
let id1 = Symbol("id");
let id2 = Symbol("id");
*!*
alert(id1 == id2); // false
*/!*
假如您熟习 Ruby 或许其他有 “Symbol” 的言语 —— 别被误导。JavaScript 的 Symbol 异乎寻常。
JavaScript 中的大多数值都支撑 string 的隐式转换。比方,我们能够 `alert` 任何值,这会起作用。Symbol 是迥殊的,它没法自动转换。
比方,这个 `alert` 将会显现毛病:
let id = Symbol(“id”);
!
alert(id); // 范例毛病:没法将 Symbol 值转换为 String。
/!
假如我们真的想显现一个 Symbol,我们须要在它上面挪用 `.toString()`,以下所示:
let id = Symbol(“id”);
!
alert(id.toString()); // Symbol(id),如今它起作用了
/!
这是一种防备杂沓的“言语庇护”,由于 String 和 Symbol 有本质上的差别,而且不应当偶然将它们互相转化。
“隐蔽”属性
Symbol 许可我们建立对象的“隐蔽”属性,代码的任何其他部份都不能偶然接见或重写这些属性。
比方,假如我们想存储 object user
的“标识符”,我们能够运用 Symbol 作为它的键:
let user = { name: "John" };
let id = Symbol("id");
user[id] = "ID Value";
alert( user[id] ); // 我们能够运用 Symbol 作为键来接见数据。
在 string "id"
上运用 Symbol("id")
有什么优点?
我们用更深切一点的示例来申明这一点。
假定另一个剧本愿望 user
中有它本身的 “id” 属性能够操纵。这多是另一个 JavaScript 库,所以这些剧本完整不知道对方是谁。
然后该剧本能够建立本身的 Symbol("id")
,以下所示:
// ...
let id = Symbol("id");
user[id] = "Their id value";
不会争执,由于 Symbol 老是差别的,纵然它们有雷同的称号。
如今请注意,假如我们运用 String "id"
而不是用 symbol,那末就会涌现争执:
let user = { name: "John" };
//我们的剧本运用 "id" 属性。
user.id = "ID Value";
// ...假如以后另一个剧本为其目标运用 "id"...
user.id = "Their id value"
// 砰!无意中重写了 id!他不是故意伤害同事的,而是如许做了!
字面量中的 Symbol
假如我们要在 object 字面量中运用 Symbol,则须要方括号。
就像如许:
let id = Symbol("id");
let user = {
name: "John",
*!*
[id]: 123 // 不仅仅是 "id:123"
*/!*
};
这是由于我们须要变量 id
的值作为键,而不是 String “id”。
Symbol 在 for..in 中被跳过
Symbolic 属性不介入 for..in
轮回。
比方:
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
*!*
for (let key in user) alert(key); // name, age (no symbols)
*/!*
// 被 Symbol 使命直接接见
alert( "Direct: " + user[id] );
这是平常“隐蔽”观点的一部份。假如另一个剧本或库在我们的对象上轮回,它不会接见一个 Symbol 范例的属性。
相反,Object.assign 同时复制字符串和标记属性:
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = Object.assign({}, user);
alert( clone[id] ); // 123
这里并不矛盾,就是如许设想的。我们的主意是当我们克隆一个 object 或兼并 object 时,一般愿望一切属性被复制(包含像 id
如许的 Symbol)。
我们只能在对象中运用 string 或 symbol 作为键,别的范例转换为 String。
比方,在作为属性键运用时,数字 `0`变成了字符串 `"0"`:
let obj = {
0: “test” // same as “0”: “test”
};
//两个 alert 都接见雷同的属性(Number 0 被转换为 String “0”)
alert( obj[“0”] ); // test
alert( obj[0] ); // test (雷同属性)
全局 symbol
正如我们所看到的,一般一切的 Symbol 都是差别的,纵然它们有雷同的名字。但偶然我们想要同一个名字的 Symbol 是雷同的实体。
比方,我们愿望在应用程序的差别部份接见雷同的 Symbol "id"
属性。
为此,存在一个全局 symbol 注册表。我们能够在个中建立 Symbol 并在稍后接见它们,它能够确保每次接见雷同称号都邑返回雷同的 Symbol。
为了在注册表中建立或读取 Symbol,请运用 Symbol.for(key)
。
该挪用会搜检全局注册表,假如有一个形貌为 key
的 Symbol,则返回该 Symbol,否则将建立一个新 Symbol(Symbol(key)
),并经由过程给定的 key
将其存储在注册表中。
比方:
// 从全局注册表中读取
let id = Symbol.for("id"); // 假如该 Symbol 不存在,则建立它
// 再次读取
let idAgain = Symbol.for("id");
// 雷同的 Symbol
alert( id === idAgain ); // true
注册表内的 Symbol 称为全局 Symbol。假如我们想要一个应用程序范围内的 Symbol,能够在代码中到处接见 —— 这就是它们的用处。
在一些编程言语中,比方 Ruby,每一个称号都有一个 symbol。
在 JavaScript 中,我们应当用全局 symbol。
Symbol.keyFor
关于全局 symbol,Symbol.for(key)
不仅按称号返回一个 symbol,而且另有一个反向挪用:Symbol.keyFor(sym)
,反过来:经由过程全局 symbol 返回一个称号。
比方:
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// 从 symbol 中猎取 name
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
Symbol.keyFor
在内部运用全局 symbol 注册表来查找 symbol 的键。所以它不适用于非全局 symbol。假如 symbol 不是全局的,它将没法找到它并返回 undefined
。
比方:
alert( Symbol.keyFor(Symbol.for("name")) ); // name, 全局 Symbol
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, 参数不是一个全局 Symbol
体系 Symbol
JavaScript 内部存在许多“体系” Symbol,我们能够运用它们来微调对象的各个方面。
它们列在熟习的 Symbol 表的范例中:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
- …等等。
比方,Symbol.toPrimitive
许可我们将对象形貌为原始值转换,我们很快就会看到它的运用。
当我们研讨响应的言语特性时,其他 Symbol 也会变得熟习起来。
总结
Symbol
是唯一标识符的基础范例
Symbol 运用 Symbol()
建立的,挪用带有一个可选的形貌。
Symbol 老是差别的值,纵然它们有雷同的称号。假如我们愿望同名 Symbol 相称,那末我们应当运用全局注册表:Symbol.for(key)
返回(假如须要的话建立)一个以 key
作为称号的全局 Symbol。Symbol.for
的屡次挪用完整返回雷同的 Symbol。
Symbol 有两个重要的运用场景:
- “隐蔽” 对象属性。假如须要将属性添加到 “属于” 另一个剧本或库的对象中,则能够建立 Symbol 并将其用作属性键。Symbol 属性不出如今
for..in
中,因而不会无意列出。别的,它不会被直接接见,由于另一个剧本没有我们的标记,所以它不会不小心干涉干与它的操纵。因而我们能够运用 Symbol 属性“秘密地”将一些东西隐蔽到我们须要的对象中,但其他人不会以对象属性的情势看到它。
- JavaScript 运用了许多体系 Symbol,这些 Symbol 能够作为
Symbol.*
接见。我们能够运用它们来转变一些内置行动。比方,在本教程的背面部份,我们将运用Symbol.iterator
来迭代,Symbol.toPrimitive
来设置对象原始值的转换等等。
从技术上说,Symbol 不是 100% 隐蔽的。有一个内置要领 Object.getOwnPropertySymbols(obj) 许可我们猎取一切的 Symbol。另有一个名为 Reflect.ownKeys(obj) 返回一切键,包含 Symbol。所以它们不是真正的隐蔽。然则大多数库、内置要领和语法结构都遵照一个配合的协定。而明白挪用上述要领的人能够很清晰他在做什么。