网站

Symbol - JavaScript | MDN

JavaScript Symbol - W3School

引入

JS 中有时需要一个绝对不重复的值来当作标识,比如给对象加一个不会冲突的属性名。

字符串可能重复,数字也可能重复,Symbol 就是为了解决这个问题而设计的,每次调用都会生成一个全新的、唯一的值。

正文

定义

定义

Symbol 是 ES6 引入的值类型,每次调用 Symbol() 都会创建一个全局唯一的值,常用作对象的属性标识。

创建 Symbol

使用 Symbol() 创建。

let id1 = Symbol();
let id2 = Symbol();
 
console.log(id1 === id2);

输出结果:

false

每次调用 Symbol() 都生成一个全新的值,即使不传任何参数,两个 Symbol 也不相等。

添加描述

Symbol() 可以接受一个字符串作为描述,但描述不影响唯一性。

let id1 = Symbol("id");
let id2 = Symbol("id");
 
console.log(id1 === id2);

输出结果:

false

即使描述完全相同,两个 Symbol 依然不相等。

描述只用于调试时辨认:

let id = Symbol("userId");
 
console.log(id.toString());

输出结果:

"Symbol(userId)"

typeof 检查

let id = Symbol("id");
 
console.log(typeof id);

输出结果:

"symbol"

用作对象属性名

Symbol 最常见的用途是作为对象的属性名,避免属性名冲突。

let id = Symbol("id");
 
let user = {
  name: "Tom",
  [id]: 10001
};
 
console.log(user[id]);
console.log(user.name);

输出结果:

10001
"Tom"

用中括号 [id] 把 Symbol 作为属性名,外面用 user[id] 来访问。

如果使用字符串做属性名,可能会和其他代码冲突:

let user = {
  name: "Tom",
  id: 10001
};

如果有另一段代码也往 user 上加 id 属性,就会覆盖。用 Symbol 就不会有这个问题。

Symbol 属性不会被 for…in 遍历到

let id = Symbol("id");
 
let user = {
  name: "Tom",
  age: 18,
  [id]: 10001
};
 
for (let key in user) {
  console.log(key);
}

输出结果:

"name"
"age"

Symbol 属性不会出现在 for…in 循环中。

同样,Object.keys() 也不会返回 Symbol 属性:

console.log(Object.keys(user));

输出结果:

["name", "age"]

如果想获取对象上的 Symbol 属性,需要用 Object.getOwnPropertySymbols()

let symbols = Object.getOwnPropertySymbols(user);
 
console.log(symbols);

输出结果:

[Symbol(id)]

Symbol.for() 共享 Symbol

Symbol() 每次调用都创建新值,而 Symbol.for() 会根据描述查找已有的 Symbol。

let id1 = Symbol.for("id");
let id2 = Symbol.for("id");
 
console.log(id1 === id2);

输出结果:

true

Symbol.for() 会在全局 Symbol 注册表中查找,如果描述相同的 Symbol 已经存在,就返回已有的那个。

Symbol.keyFor()

Symbol.keyFor() 可以获取通过 Symbol.for() 创建的 Symbol 的描述。

let id = Symbol.for("userId");
 
console.log(Symbol.keyFor(id));

输出结果:

"userId"

注意 Symbol.keyFor() 只对 Symbol.for() 创建的 Symbol 有效:

let id = Symbol("userId");
 
console.log(Symbol.keyFor(id));

输出结果:

undefined

内置 Symbol

JS 内置了一些 Symbol,用于自定义对象的内部行为。

内置 Symbol作用
Symbol.iterator定义对象的迭代行为
Symbol.toPrimitive定义对象转原始值的行为
Symbol.toStringTag自定义 Object.prototype.toString 的标签
let list = {
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    let data = this.data;
 
    return {
      next() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        }
 
        return { done: true };
      }
    };
  }
};
 
for (let item of list) {
  console.log(item);
}

输出结果:

1
2
3

Symbol 不能参与运算

Symbol 不能和数字、字符串等进行运算。

let id = Symbol("id");
 
console.log(id + 1);

这种写法会报错。

console.log(id + "text");

这种写法也会报错。

如果需要转成字符串,要显式调用:

let id = Symbol("id");
 
console.log(id.toString());
console.log(String(id));

输出结果:

"Symbol(id)"
"Symbol(id)"

常见写法

写法作用
Symbol()创建唯一 Symbol
Symbol("描述")创建带描述的 Symbol
Symbol.for("key")创建或获取共享 Symbol
Symbol.keyFor(sym)获取共享 Symbol 的描述
obj[sym]用 Symbol 作为属性名访问值
Object.getOwnPropertySymbols(obj)获取对象上所有 Symbol 属性

例子

防止属性名冲突

let version = Symbol("version");
 
let config = {
  host: "localhost",
  port: 3000,
  [version]: "1.0.0"
};
 
console.log(config[version]);
console.log(Object.keys(config));

输出结果:

"1.0.0"
["host", "port"]

Symbol 属性不会影响普通的键名遍历。

使用共享 Symbol

let role = Symbol.for("userRole");
 
let user1 = { name: "Tom", [role]: "admin" };
let user2 = { name: "Jerry", [role]: "user" };
 
console.log(user1[role]);
console.log(user2[role]);
 
console.log(user1[Symbol.for("userRole")]);

输出结果:

"admin"
"user"
"admin"

通过 Symbol.for() 在不同地方使用同一个 Symbol 标识。

特点

  • Symbol() 每次调用都创建唯一值
  • 描述只是为了调试方便,不影响唯一性
  • Symbol.for() 可以创建共享的 Symbol
  • Symbol 常用作对象的属性名,避免冲突
  • Symbol 属性不会出现在 for…inObject.keys()
  • Symbol 不能参与运算,转字符串要显式调用
  • typeof 对 Symbol 返回 "symbol"

理解

可以把 Symbol 理解成每台机器自动生成的序列号。即使你在两台机器上写同样的描述,序列号也不一样,保证全局唯一。

它最常见的用途就是给对象加一个”不会和别人撞名”的属性。比如你写了一个库,往用户传入的对象上加内部数据,用 Symbol 做属性名就不用担心覆盖用户自己的属性。

日常开发中 Symbol 用得不是特别多,但在写框架、库或者需要定义对象内部行为时会很有用。

引出

理解了 Symbol 后,可以继续学习 JS 引用类型,了解与值类型相对的引用类型。

也可以学习 JS 对象,看看 Symbol 在实际对象中是怎么使用的。