icon
符号

符号

Symbol 是 ES6 引入的原始数据类型,用于创建唯一的标识符。

基本用法

// 创建 Symbol
const sym1 = Symbol();
const sym2 = Symbol('description');
const sym3 = Symbol('description');

console.log(sym1);        // Symbol()
console.log(sym2);        // Symbol(description)
console.log(sym2 === sym3); // false (每个 Symbol 都是唯一的)

特性

  1. 唯一性
    • 每个 Symbol 都是唯一的,即使描述相同
const sym1 = Symbol('id');
const sym2 = Symbol('id');
console.log(sym1 === sym2); // false
  1. 原始类型
    • Symbol 是原始类型,不是对象
const sym = Symbol('test');
console.log(typeof sym); // 'symbol'
  1. 不可枚举
    • Symbol 属性默认不可枚举
const obj = {
  name: 'Alice',
  [Symbol('id')]: 123
};

console.log(Object.keys(obj));        // ['name']
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id)]

Symbol.for 和 Symbol.keyFor

  1. Symbol.for
    • 在全局 Symbol 注册表中查找或创建 Symbol
const sym1 = Symbol.for('key');
const sym2 = Symbol.for('key');
console.log(sym1 === sym2); // true (同一个 Symbol)

const sym3 = Symbol('key');
console.log(sym1 === sym3); // false
  1. Symbol.keyFor
    • 返回全局 Symbol 的 key
const sym = Symbol.for('myKey');
console.log(Symbol.keyFor(sym)); // 'myKey'

const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined

内置 Symbol

  1. Symbol.iterator
    • 定义对象的默认迭代器
const obj = {
  values: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.values.length) {
          return { value: this.values[index++], done: false };
        }
        return { done: true };
      }
    };
  }
};

for (const value of obj) {
  console.log(value); // 1, 2, 3
}
  1. Symbol.toStringTag
    • 自定义对象的 toString 标签
class MyClass {
  get [Symbol.toStringTag]() {
    return 'MyClass';
  }
}

const obj = new MyClass();
console.log(obj.toString()); // '[object MyClass]'
  1. Symbol.toPrimitive
    • 自定义对象转换为原始值的行为
const obj = {
  value: 10,
  [Symbol.toPrimitive](hint) {
    if (hint === 'number') {
      return this.value;
    }
    if (hint === 'string') {
      return String(this.value);
    }
    return this.value;
  }
};

console.log(+obj);        // 10 (number)
console.log(String(obj)); // '10' (string)
console.log(obj + 5);     // 15 (default)
  1. Symbol.hasInstance
    • 自定义 instanceof 的行为
class MyArray {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}

console.log([] instanceof MyArray); // true
console.log({} instanceof MyArray); // false
  1. Symbol.species
    • 指定创建衍生对象的构造函数
class MyArray extends Array {
  static get [Symbol.species]() {
    return Array;
  }
}

const myArr = new MyArray(1, 2, 3);
const mapped = myArr.map(x => x * 2);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true
  1. Symbol.isConcatSpreadable
    • 控制数组在 concat 时是否展开
const arr1 = [1, 2];
const arr2 = [3, 4];
arr2[Symbol.isConcatSpreadable] = false;

console.log(arr1.concat(arr2)); // [1, 2, [3, 4]]

实际应用

  1. 私有属性
const _name = Symbol('name');
const _age = Symbol('age');

class Person {
  constructor(name, age) {
    this[_name] = name;
    this[_age] = age;
  }

  getName() {
    return this[_name];
  }
}

const person = new Person('Alice', 25);
console.log(person.getName()); // 'Alice'
console.log(person[_name]);    // 'Alice' (仍可访问,但需要 Symbol)
console.log(Object.keys(person)); // [] (不可枚举)
  1. 元数据存储
const METADATA = Symbol('metadata');

function addMetadata(obj, data) {
  obj[METADATA] = data;
}

function getMetadata(obj) {
  return obj[METADATA];
}

const obj = { name: 'Alice' };
addMetadata(obj, { created: Date.now(), version: '1.0' });
console.log(getMetadata(obj)); // { created: ..., version: '1.0' }
console.log(Object.keys(obj)); // ['name'] (不影响普通属性)
  1. 防止属性冲突
// 库 A
const ID = Symbol('id');
function setID(obj, id) {
  obj[ID] = id;
}

// 库 B
const ID = Symbol('id'); // 不同的 Symbol,不会冲突
function setID(obj, id) {
  obj[ID] = id;
}

const obj = {};
setID(obj, 1); // 库 A
setID(obj, 2); // 库 B
// 两个 ID 互不干扰
  1. 定义常量
const STATUS = {
  PENDING: Symbol('pending'),
  FULFILLED: Symbol('fulfilled'),
  REJECTED: Symbol('rejected')
};

function handleStatus(status) {
  switch (status) {
    case STATUS.PENDING:
      return '处理中';
    case STATUS.FULFILLED:
      return '已完成';
    case STATUS.REJECTED:
      return '已拒绝';
  }
}
  1. 实现单例模式
const INSTANCE = Symbol('instance');

class Singleton {
  constructor() {
    if (Singleton[INSTANCE]) {
      return Singleton[INSTANCE];
    }
    Singleton[INSTANCE] = this;
    return this;
  }
}

const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2); // true
  1. 自定义迭代行为
class Collection {
  constructor(items) {
    this.items = items;
  }

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

const collection = new Collection([1, 2, 3]);
for (const item of collection) {
  console.log(item); // 1, 2, 3
}

Symbol 属性访问

const sym = Symbol('key');
const obj = {
  [sym]: 'value',
  name: 'Alice'
};

// 访问 Symbol 属性
console.log(obj[sym]); // 'value'

// 获取所有 Symbol 属性
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(key)]

// 获取所有属性(包括 Symbol)
console.log(Reflect.ownKeys(obj)); // ['name', Symbol(key)]

// Symbol 属性不可枚举
console.log(Object.keys(obj)); // ['name']
for (const key in obj) {
  console.log(key); // 'name' (不包括 Symbol)
}

注意事项

  1. Symbol 不能使用 new
const sym = new Symbol(); // TypeError: Symbol is not a constructor
  1. Symbol 不能转换为字符串
const sym = Symbol('test');
String(sym);        // 'Symbol(test)'
sym.toString();     // 'Symbol(test)'
// sym + '';        // TypeError: Cannot convert a Symbol value to a string
  1. Symbol 可以转换为布尔值
const sym = Symbol();
console.log(Boolean(sym)); // true
if (sym) {
  console.log('truthy');
}
  1. JSON.stringify 忽略 Symbol
const obj = {
  name: 'Alice',
  [Symbol('id')]: 123
};

console.log(JSON.stringify(obj)); // '{"name":"Alice"}'
  1. Symbol 属性不会被继承
const sym = Symbol('key');
class Parent {
  [sym] = 'parent';
}

class Child extends Parent {}

const child = new Child();
console.log(child[sym]); // 'parent' (可以访问,因为是实例属性)

常用内置 Symbol 总结

| Symbol | 用途 | |--------|------| | Symbol.iterator | 定义默认迭代器 | | Symbol.toStringTag | 自定义 toString 标签 | | Symbol.toPrimitive | 自定义类型转换 | | Symbol.hasInstance | 自定义 instanceof | | Symbol.species | 指定衍生对象构造函数 | | Symbol.isConcatSpreadable | 控制 concat 展开 | | Symbol.match | 自定义 match 行为 | | Symbol.replace | 自定义 replace 行为 | | Symbol.search | 自定义 search 行为 | | Symbol.split | 自定义 split 行为 |

最佳实践

  1. 使用描述性字符串
const USER_ID = Symbol('user.id');
const SESSION_TOKEN = Symbol('session.token');
  1. 导出 Symbol 以便外部访问
// module.js
export const PRIVATE_KEY = Symbol('private');

// 使用时
import { PRIVATE_KEY } from './module.js';
obj[PRIVATE_KEY] = 'value';
  1. 使用 Symbol.for 共享 Symbol
// 跨模块共享
const SHARED_KEY = Symbol.for('shared.key');