Symbol

22 天前(已编辑)
3

Symbol

什么是 Symbol?

先看一个问题:

如果你写了一个类,想给类添加一个"私有"属性,防止别人直接用 . 访问,你会怎么做?

class User {
  constructor() {
    this.name = "张三";
    this._password = "123456"; // 下划线约定:这是私有变量
  }
}

const user = new User();
console.log(user._password); // 123456 —— 完全没有隐私!

Symbol 就是来解决这个问题的!

class User {
  constructor() {
    this.name = "张三";
    const privateData = Symbol("password"); // 用 Symbol 作为隐藏的 key
    this[privateData] = "123456";
  }

  getPassword() {
    return this[Symbol("password")]; // 错误!每次创建新的 Symbol,不一样!
  }
}

等等,这样还是不行... 所以 Symbol 的核心特点是:

Symbol 的三大特性

  1. 唯一性Symbol() 每次调用都创建全新的值,即使描述相同也不相等
  2. 隐藏性:用 Symbol 作为对象的 key,不会被 for...inObject.keys() 遍历到
  3. 不可枚举:Symbol 作为 key 的属性是"隐藏"的
const sym1 = Symbol("key");
const sym2 = Symbol("key");
console.log(sym1 === sym2); // false —— 每次都是全新的!

const obj = { [sym1]: "value" };
console.log(obj[sym2]); // undefined —— sym1 和 sym2 不一样!
console.log(Object.keys(obj)); // [] —— 遍历不到!

// 正确用法:保存同一个 Symbol
const password = Symbol("password");
obj[password] = "secret";
obj[password]; // "secret"

Symbol 解决了什么问题?

场景不用 Symbol用 Symbol
模拟私有属性_password 仍可被访问属性隐藏,不可枚举
避免字符串键冲突可能覆盖他人添加的属性唯一值,不会冲突
定义"魔法字符串"用字符串,容易拼错用 Symbol,精准控制

快速上手

// 创建一个 Symbol
const sym = Symbol("可选的描述");

// 查看类型
typeof sym; // "symbol"

// 不能用 new
new Symbol(); // TypeError!

// 可以用作对象的 key
const obj = { [sym]: "hello" };
obj[sym]; // "hello"

一、Symbol 构造函数

1.1 基本特性

  • 构造函数:%Symbol%
  • 全局对象属性Symbol
  • 调用方式:作为函数调用(不使用 new
  • 不可继承:不能作为类的父类被继承

1.2 Symbol([description])

// 语法
Symbol(description?)

参数:

  • description: 可选的描述字符串

算法步骤:

  1. 如果 NewTarget 不是 undefined,抛出 TypeError
  2. 如果 descriptionundefineddescStringundefined
  3. 否则,将 description 转换为字符串
  4. 返回一个新的 Symbol,其 [[Description]]descString
// 正确用法
const sym1 = Symbol();
const sym2 = Symbol("description");
const sym3 = Symbol("same description"); // 与 sym2 不相等!

// 错误用法
const sym = new Symbol(); // TypeError: Symbol is not a constructor

二、Symbol 构造函数的属性

2.1 静态属性一览

属性描述用途
Symbol.asyncIterator知名符号异步迭代器
Symbol.hasInstance知名符号instanceof 检测
Symbol.isConcatSpreadable知名符号Array.prototype.concat 展开控制
Symbol.iterator知名符号迭代器
Symbol.match知名符号String.prototype.match
Symbol.matchAll知名符号String.prototype.matchAll
Symbol.replace知名符号String.prototype.replace
Symbol.search知名符号String.prototype.search
Symbol.species知名符号创建派生对象
Symbol.split知名符号String.prototype.split
Symbol.toPrimitive知名符号转换为原始值
Symbol.toStringTag知名符号对象字符串描述
Symbol.unscopables知名符号with 语句排除

2.2 静态方法

Symbol.for(key)

在全局 Symbol 注册表中注册或获取 Symbol。

特点:

  • 跨 realm 共享(所有realm共享同一个 GlobalSymbolRegistry)
  • 相同 key 返回相同 Symbol
  • 注册后永不被垃圾回收
const sym1 = Symbol.for("global");
const sym2 = Symbol.for("global");
console.log(sym1 === sym2); // true

Symbol.keyFor(sym)

从全局 Symbol 注册表中获取 key。

const sym = Symbol.for("myKey");
console.log(Symbol.keyFor(sym)); // "myKey"

// 普通 symbol 会返回 undefined
const sym2 = Symbol("local");
console.log(Symbol.keyFor(sym2)); // undefined

三、Symbol.prototype 属性

3.1 constructor

Symbol.prototype.constructor === Symbol // true

3.2 description

获取 Symbol 的描述字符串(访问器属性)。

const sym = Symbol("myDescription");
console.log(sym.description); // "myDescription"

const sym2 = Symbol();
console.log(sym2.description); // undefined

3.3 toString()

返回 Symbol 的字符串描述。

const sym = Symbol("test");
sym.toString(); // "Symbol(test)"

const sym2 = Symbol();
sym2.toString(); // "Symbol()"

内部实现:SymbolDescriptiveString

  1. 获取 sym.[[Description]]
  2. 如果是 undefined,设为空字符串
  3. 返回 "Symbol(" + desc + ")"

3.4 valueOf()

返回 Symbol 本身。

const sym = Symbol("test");
sym.valueOf() === sym; // true

3.5 Symbol.toPrimitive

Symbol 的原始值转换。

const sym = Symbol("test");
+sym; // TypeError: Cannot convert a Symbol value to a primitive

注意: Symbol 不能转换为数字或字符串,会抛出 TypeError。

3.6 [Symbol.toStringTag]

默认值为 "Symbol"

const sym = Symbol("test");
sym[Symbol.toStringTag]; // "Symbol"

Object.prototype.toString.call(sym); // "[object Symbol]"

四、Symbol 实例

4.1 实例内部槽

内部槽描述
[[SymbolData]]存储实际的 Symbol 原始值
[[Prototype]]指向 Symbol.prototype

4.2 实例继承关系

Symbol 实例
  ↓ [[Prototype]]
Symbol.prototype
  ↓ [[Prototype]]
Object.prototype
  ↓ [[Prototype]]
null

五、知名符号详解

5.1 Symbol.iterator

用于定义对象的默认迭代器。

const iterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
};

for (const item of iterable) {
  console.log(item); // 1, 2, 3
}

5.2 Symbol.asyncIterator

用于定义对象的默认异步迭代器。

const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0;
    return {
      next() {
        return Promise.resolve({
          value: ++i,
          done: i > 3
        });
      }
    };
  }
};

for await (const item of asyncIterable) {
  console.log(item);
}

5.3 Symbol.hasInstance

自定义 instanceof 行为。

class MyArray {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}

[] instanceof MyArray; // true

5.4 Symbol.toPrimitive

自定义原始值转换。

const obj = {
  [Symbol.toPrimitive](hint) {
    if (hint === "number") return 42;
    if (hint === "string") return "answer";
    return "default";
  }
};

+obj; // 42
`${obj}`; // "answer"
obj + ""; // "default"

5.5 Symbol.species

用于创建派生对象的静态访问器。

class MyArray extends Array {
  static get [Symbol.species]() {
    return Array;
  }
}

const arr = MyArray.from([1, 2, 3]);
const mapped = arr.map(x => x * 2);
// mapped 是普通 Array,不是 MyArray

5.6 Symbol.toStringTag

自定义 Object.prototype.toString 的返回值。

class MyClass {
  get [Symbol.toStringTag]() {
    return "MyClass";
  }
}

Object.prototype.toString.call(new MyClass()); // "[object MyClass]"

5.7 Symbol.isConcatSpreadable

控制 Array.prototype.concat 是否展开。

const arr = [1, 2];
const notSpreadable = {
  length: 2,
  0: 3,
  1: 4,
  [Symbol.isConcatSpreadable]: false
};

arr.concat(notSpreadable); // [[1, 2], [3, 4]]

六、全局 Symbol 注册表

6.1 GlobalSymbolRegistry

  • 一个全局可用的 append-only List
  • 所有 realm 共享
  • 在任何 ECMAScript 代码执行前初始化为空 List
  • 元素为 Record 结构:{ [[Key]]: String, [[Symbol]]: Symbol }

6.2 抽象操作:KeyForSymbol(sym)

// 伪代码
function KeyForSymbol(sym) {
  for (const e of GlobalSymbolRegistry) {
    if (SameValue(e[[Symbol]], sym)) {
      return e[[Key]];
    }
  }
  return undefined;
}

七、常见错误

7.1 不能用作构造函数

new Symbol(); // TypeError

7.2 不能强制转换

String(Symbol("test")); // "Symbol(test)"
// 但
Symbol("test") + ""; // TypeError

+Symbol("test"); // TypeError
Symbol("test") * 2; // TypeError

7.3 不能作为对象键(字面量)

const obj = {
  [Symbol("key")]: "value" // 可以
};

// 但不能通过点运算符访问

八、实用技巧

8.1 创建唯一值

const uniqueKey = Symbol("description");
const obj = {};
obj[uniqueKey] = "private data";

8.2 模拟私有属性

const _private = Symbol("private");

class MyClass {
  constructor() {
    this[_private] = "secret";
  }
}

8.3 避免命名冲突

const mySymbol = Symbol("method");
obj[mySymbol] = () => "custom";

8.4 检查是否为 Symbol

const sym = Symbol("test");
console.log(typeof sym); // "symbol"
console.log(sym instanceof Symbol); // true

九、总结

  1. 创建方式

    • Symbol()
      • 创建本地 Symbol
    • Symbol.for()
      • 创建/获取全局注册 Symbol
  2. 特性

    • 唯一性:相同描述的 Symbol 也不相等
    • 不可枚举:不会被 for...in、Object.keys 枚举
    • 不可强制转换:不能转为数字
  3. 知名符号

    • 共 13 个,用于自定义语言行为
    • 都是不可写、不可枚举、不可配置
  4. 注册表

    • Symbol.for() / Symbol.keyFor() 成对使用
    • 注册的 Symbol 跨 realm 可访问

参考

  • ECMAScript 2025 Language Specification Section 20.4
  • Symbol 相关内置对象

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...