目录

🗽 JavaScript symbol 类型

在 JavaScript 规范中,只有两种原始类型可以作为对象的属性键(key)。

  • 字符串 (使用其他的原始类型,例如数字或者布尔类型,都会自动转化为字符串)
  • symbol 类型

# symbol 唯一标识符

语法:可以使用 Symbol() 创建 symbol 类型的值,返回给变量一个唯一的标识符的值。在括号中的参数可以是一个描述(描述可以相同)。

🌰 例子: let id = Symbol() / let id = Symbol("id")

symbol 保证是唯一的。即使创建了许多具有相同描述的 symbol,它们的值也是不同。描述只是一个标签,不影响任何东西。

🌰 例子:

let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false
1
2
3
4

symbol 类型不会被自动转换为字符串。JavaScript 中的大多数值都支持字符串的隐式转换。例如,可以 alert 任何值(自动转换为字符串输出),都可以生效。symbol 比较特殊,它不会被自动转换。

🌰 例子:

let id = Symbol("id");
alert(id); // 类型错误:无法将 symbol 值转换为字符串。
1
2

可以使用强制转换解决:(将 symbol 转换为字符串)

alert(id.toString())
1

或者只显示描述:

et id = Symbol("id");
alert(id.description)
1
2

# symbol 隐藏属性

symbol 允许我们创建对象的「隐藏」属性,代码的任何其他部分都不能意外访问或重写这些属性。

🌰 例子:

let user {
  name = 'Simon'
}

let id = Sysbol('id')
user.[id] = 1 // 添加新的属性
alert(user[id])
1
2
3
4
5
6
7

user 对象属于其他的代码,那些代码也会使用这个对象,所以我们不应该在它上面直接添加任何字段,这样很不安全。但是你添加的 symbol 属性不会被意外访问到,第三方代码根本不会看到它,所以使用 symbol 基本上不会有问题。

🌰 例子:假设另一个脚本希望在 user 中有自己的标识符,以实现自己的目的。这可能是另一个 JavaScript 库,因此脚本之间完全不了解彼此。

然后该脚本可以创建自己的 Symbol("id") ,像这样:

let id = Symbol('id')
user[id] = 'their id value'
1
2

虽然有相同的名称,但是 symbol 类型保证了唯一性。

如果仅仅使用字符串作为属性名:

let user = { 
  // ... 
}
// 一个脚本中
user.id = 'id1'
// 另一个脚本中也添加id,产生冲突
user.id = 'id2'
1
2
3
4
5
6
7

# 在对象字面量中使用 symbol

在对象 { … } 字面量中使用 symbol 时,需要使用方括号把它括起来。

🌰 例子:

let id = Symbol('id')
let user = {
  name: 'Simon',
  [id]: 123
}
1
2
3
4
5

这里需要 symbol 类型的 id 作为对象属性的键, 所以需要使用方括号 [] 区分开字符串 ‘id’ 与 symbol。

# 在遍历循环中的 symbol

⭐️ symbol 属性不参与 for..in 循环。(同理还有 Object.keys() )这是一般「隐藏符号属性」原则的一部分,如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。

🌰 例子:

let id = Symbol('id')
let user = {
  name: 'Simon',
  [id]: 123
}

for (let key in user) alert(key) // 只有name

alert(user[id]) // 单独可以直接访问
1
2
3
4
5
6
7
8
9

但是在 Object.assign() 中,会同时复制字符串和 symbol 属性。

🌰 例子:

let id = Symbol("id");
let user = {
  [id]: 123
};

let clone = Object.assign({}, user);

alert( clone[id] ); // 123
1
2
3
4
5
6
7
8

# 全局 symbol

如前所述,通常所有的 symbol 都是不同的,即使它们有相同的名字。但有时想要名字相同的 symbol 具有相同的实体时,例如,应用程序的不同部分想要访问的 symbol "id" 指的是完全相同的属性。

为了实现这一点,可以使用 全局 symbol 注册表可以在其中创建 symbol 并在稍后访问它们,它可以确保每次访问相同名字的 symbol 时,返回的都是相同的 symbol

# Symbol.for(key)

  • 从注册表读取(不存在则创建) symbol ,使用 Symbol.for(key)

    (该调用会检查全局注册表,如果有一个描述为 key 的 symbol,则返回该 symbol,否则将创建一个新 symbol( Symbol(key) ),并通过给定的 key 将其存储在注册表中。)

  • 注册表中的 symbol 被称为 全局 symbol。如果我们想要一个应用程序范围内的 symbol,可以在代码中随处访问 —— 这就是它们的用途。

🌰 例子:

let id = Symbol.for('id')
let idAgain = Symbol.for('id')

alert(id === idAgain) // true
1
2
3
4

# Symbol.keyFor

  • Symbol.keyFor(symbol) ,它的作用与 Symbol.for(key) 完全反过来:通过全局 symbol 返回一个名字。

  • 作用是查找 symbol 的键。所以它不适用于非全局 symbol。如果 symbol 不是全局的,它将无法找到它并返回 undefined

  • 任何 symbol 都具有 description 属性。

🌰 例子:

// 通过 name 获取 symbol
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// 通过 symbol 获取 name
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
1
2
3
4
5
6
7

🌰 例子:(非全局 symbol)

let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");

alert( Symbol.keyFor(globalSymbol) ); // name,全局 symbol
alert( Symbol.keyFor(localSymbol) ); // undefined,非全局

alert( localSymbol.description ); // name
1
2
3
4
5
6
7

# JavaScript 系统 symbol

avaScript 内部有很多「系统 symbol」,可以使用它们来微调对象的各个方面。它们都被列在了 众所周知的 symbol (opens new window) 表的规范中:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • …… 等等。

# symbol 总结

  • symbol唯一标识符的基本类型,使用带有可选描述(name)的 Symbol() 调用创建的。
  • symbol 总是 不同的值,即使它们有相同的名字。如果希望同名的 symbol 相等,那么应该使用全局注册表: Symbol.for(key) 返回(如果需要的话则创建)一个以 key 作为名字的全局 symbol。使用 Symbol.for 多次调用 key 相同的 symbol 时,返回的就是同一个 symbol。
  • symbol 不是 100% 隐藏的。有一个内建方法 Object.getOwnPropertySymbols(obj) (opens new window) 允许我们获取所有的 symbol。还有一个名为 Reflect.ownKeys(obj) (opens new window) 的方法可以返回一个对象的 所有 键,包括 symbol。所以它们并不是真正的隐藏。

使用 symbol 的场景:

📢 上次更新: 2022/09/02, 10:18:16