JavaScript Symbol 与知名符号

深入解析 JavaScript Symbol 类型:符号的创建与特性、知名符号(Well-Known Symbols)的元编程能力,以及如何使用 Symbol 实现私有属性和自定义行为。

为什么 Symbol 是 JavaScript 的独特类型

JavaScript 的 Symbol 是 ES6 引入的一种原始数据类型,代表一个唯一不可重复的值。与字符串不同,每个 Symbol 都是唯一的,这让它成为实现私有属性、扩展内置行为的理想选择。

理解 Symbol,特别是 Well-Known Symbols,是理解 JavaScript 元编程能力的关键。


一、Symbol 基础

1.1 创建 Symbol

// 使用 Symbol() 创建
const sym1 = Symbol();
const sym2 = Symbol();

console.log(sym1 === sym2); // false — 每个 Symbol 都唯一

// 可选描述
const sym = Symbol('description');
console.log(sym.description); // 'description'

1.2 Symbol 的特性

// Symbol 是原始类型
typeof Symbol(); // 'symbol'

// Symbol 不可被 for...in 遍历
const obj = { [Symbol()]: 'value' };
for (let key in obj) {
  console.log(key); // 不输出(Symbol 属性被忽略)
}

// Object.keys() 也获取不到 Symbol 属性
Object.keys(obj); // []

// 需要用 Object.getOwnPropertySymbols
Object.getOwnPropertySymbols(obj); // [Symbol()]

1.3 Symbol 注册表

// Symbol.for() 在全局注册表创建/获取 Symbol
const globalSym = Symbol.for('global');
const anotherGlobalSym = Symbol.for('global');

console.log(globalSym === anotherGlobalSym); // true — 同一个 Symbol

// Symbol.keyFor() 获取全局 Symbol 的键
console.log(Symbol.keyFor(globalSym)); // 'global'

// 普通 Symbol 没有键
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined

二、Well-Known Symbols

Well-Known Symbols 是 JavaScript 内置的 Symbol,用于定义对象在特定操作中的行为。

2.1 Symbol.iterator

定义对象的默认迭代器:

const collection = {
  items: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.items.length) {
          return { value: this.items[index++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

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

2.2 Symbol.toStringTag

自定义 Object.prototype.toString 的输出:

class Person {
  get [Symbol.toStringTag]() {
    return 'Person';
  }
}

const p = new Person();
console.log(p.toString()); // '[object Person]'
console.log(Object.prototype.toString.call(p)); // '[object Person]'

2.3 Symbol.hasInstance

自定义 instanceof 的行为:

class EvenNumber {
  static [Symbol.hasInstance](num) {
    return typeof num === 'number' && num % 2 === 0;
  }
}

console.log(4 instanceof EvenNumber); // true
console.log(3 instanceof EvenNumber); // false

2.4 Symbol.toPrimitive

自定义对象的类型转换:

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }

  [Symbol.toPrimitive](hint) {
    if (hint === 'number') {
      return this.celsius;
    }
    if (hint === 'string') {
      return `${this.celsius}°C`;
    }
    return this.celsius;
  }
}

const temp = new Temperature(25);
console.log(+temp);      // 25 (number)
console.log(`${temp}`);  // '25°C' (string)
console.log(temp + 0);   // 25 (default)

2.5 Symbol.species

定义 Array 等方法的返回类型:

class MyArray extends Array {
  static get [Symbol.species]() {
    return Array; // 方法返回普通 Array
  }
}

const arr = new MyArray(1, 2, 3);
const mapped = arr.map(x => x * 2);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true

三、使用 Symbol 实现私有属性

3.1 Symbol 作为私有属性名

const _password = Symbol('password');
const _authenticate = Symbol('authenticate');

class User {
  constructor(name, password) {
    this.name = name;
    this[_password] = password;
  }

  [_authenticate](input) {
    return this[_password] === input;
  }
}

const user = new User('Alice', 'secret');
console.log(user.name); // 'Alice'
console.log(user._password); // undefined — 访问不到
console.log(user[Symbol.for('password')]); // undefined — 不是同一个 Symbol

3.2 Symbol 私有属性的限制

Symbol 私有不是真正的私有——可以通过 Object.getOwnPropertySymbols 访问:

const symbols = Object.getOwnPropertySymbols(user);
console.log(user[symbols[0]]); // 'secret' — 可以访问

真正的私有属性应该用 ES2022 的 #private 语法


四、常见 Well-Known Symbols 列表

Symbol 用途
Symbol.iterator 定义默认迭代器
Symbol.asyncIterator 定义异步迭代器
Symbol.toStringTag 自定义 toString 输出
Symbol.hasInstance 自定义 instanceof
Symbol.toPrimitive 自定义类型转换
Symbol.species 定义方法返回类型
Symbol.isConcatSpreadable 控制 concat 是否展开
Symbol.unscopables 控制 with 语句的行为
Symbol.match 自定义 matchAll 和 String.prototype.match
Symbol.replace 自定义 String.prototype.replace
Symbol.search 自定义 String.prototype.search
Symbol.split 自定义 String.prototype.split

五、面试高频考点

考点 1:Symbol 的特性

Symbol 是唯一不可重复的原始类型,不能被 for...in 遍历,可以通过 Symbol.for 在全局注册表中共享。

考点 2:Well-Known Symbols 的作用

Well-Known Symbols 定义对象在内置操作中的行为,如迭代、类型转换、instanceof 等。

考点 3:Symbol 与私有属性

Symbol 可以作为属性名实现"伪私有",但不是真正的私有,可以通过 Object.getOwnPropertySymbols 访问。


延展阅读