你知道JavaScript Symbol类型怎么用吗
symbol 类型
根据规范,对象的属性键只能是字符串类型或者 symbol 类型。不是 number,也不是 boolean,只有字符串或 symbol 这两种类型。
到目前为止,我们只见过字符串。现在我们来看看 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 是不同的。
注意:symbol 不会被自动转换为字符串
javascript 中的大多数值都支持字符串的隐式转换。例如,我们可以 alert 任何值,都可以生效。symbol 比较特殊,它不会被自动转换。
例如,这个 alert 将会提示出错:
let id = symbol("id"); alert(id); // 类型错误:无法将 symbol 值转换为字符串。
这是一种防止混乱的“语言保护”,因为字符串和 symbol 有本质上的不同,不应该意外地将它们转换成另一个。
如果我们真的想显示一个 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 作为键来访问数据
在字符串 "id" 上使用 symbol("id") 有什么好处?
因为 user 属于另一个代码,另一个代码也使用它执行一些操作,所以我们不应该在它上面加任何字段,这样很不安全。但是 symbol 不会被意外访问到,所以第三方代码看不到它,所以使用 symbol 也许不会有什么问题。
另外,假设另一个脚本希望在 user 中有自己的标识符,以实现自己的目的。这可能是另一个 javascript 库,因此脚本之间完全不了解彼此。
然后该脚本可以创建自己的 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" };
这是因为我们需要变量 id 的值作为键,而不是字符串 "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] );
object.keys(user) 也会忽略它们。这是一般“隐藏符号属性”原则的一部分。如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。
相反,object.assign 会同时复制字符串和 symbol 属性:
let id = symbol("id"); let user = { [id]: 123 }; let clone = object.assign({}, user); alert( clone[id] ); // 123
这里并不矛盾,就是这样设计的。这里的想法是当我们克隆或者合并一个 object 时,通常希望 所有 属性被复制(包括像 id 这样的 symbol)。
其他类型的属性键被强制为字符串:
我们只能在对象中使用字符串或 symbol 作为键,其它类型会被转换为字符串。
例如,在作为属性键使用时,数字 0 变成了字符串 "0":
let obj = { 0: "test" // 和 "0": "test" 一样 }; // 两个 alert 都访问相同的属性(number 0 被转换为字符串 "0") alert( obj["0"] ); // test alert( obj[0] ); // test(同一个属性)
全局 symbol
正如我们所看到的,通常所有的 symbol 都是不同的,即使它们有相同的名字。但有时我们想要名字相同的 symbol 具有相同的实体。例如,应用程序的不同部分想要访问的 symbol "id" 指的是完全相同的属性。
为了实现这一点,这里有一个 全局 symbol 注册表。我们可以在其中创建 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:
在一些编程语言中,例如 ruby,每个名字都有一个 symbol。
正如我们所看到的,在 javascript 中,全局 symbol 也是这样的。
symbol.keyfor
对于全局 symbol,不仅有 symbol.for(key) 按名字返回一个 symbol,还有一个反向调用:symbol.keyfor(sym),它的作用完全反过来:通过全局 symbol 返回一个名字。
例如:
// 通过 name 获取 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。
也就是说,任何 symbol 都具有 description 属性。
例如:
let globalsymbol = symbol.for("name"); let localsymbol = symbol("name"); alert( symbol.keyfor(globalsymbol) ); // name,全局 symbol alert( symbol.keyfor(localsymbol) ); // undefined,非全局 alert( localsymbol.description ); // name
系统 symbol
javascript 内部有很多“系统” symbol,我们可以使用它们来微调对象的各个方面。
它们都被列在了众所周知的 symbol 表的规范中:
- symbol.hasinstance
- symbol.isconcatspreadable
- symbol.iterator
- symbol.toprimitive
- ……等等。
例如,symbol.toprimitive 允许我们将对象描述为原始值转换。我们很快就会看到它的使用。
当我们研究相应的语言特征时,我们对其他的 symbol 也会慢慢熟悉起来。
总结
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。所以它们并不是真正的隐藏。但是大多数库、内置方法和语法结构都没有使用这些方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: js实现多个标题吸顶效果
下一篇: 揭秘:为什么古代人都喜欢围观砍头呢?