什么是 Symbol

Symbol 是 ES6 引入的一种新的原始数据类型,它表示一个唯一的标识符。 每个 Symbol 值都是唯一的,即使是用相同的描述创建的 Symbol 也不相等。

const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false,每个Symbol都是唯一的
const sym3 = Symbol("description");
const sym4 = Symbol("description");
console.log(sym3 === sym4); // false,即使描述相同也不相等

为什么需要 Symbol

在 ES6 之前,对象的属性名只能是字符串。这会导致属性名冲突的问题,特别是在:

  • 扩展第三方库对象时
  • 多个开发者协作时
  • 需要添加”私有”属性时
// 传统方式可能出现的问题
const person = { name: "Kayn" };
person.name = "Alice"; // 意外覆盖了原有属性
// 使用Symbol避免冲突
const nameSymbol = Symbol("name");
person[nameSymbol] = "Bob"; // 不会影响原有的name属性
console.log(person.name); // "Alice"
console.log(person[nameSymbol]); // "Bob"

创建 Symbol

基本创建方式

// 创建一个Symbol
const sym = Symbol();
// 创建带描述的Symbol(描述仅用于调试)
const namedSym = Symbol("mySymbol");
console.log(namedSym.toString()); // "Symbol(mySymbol)"

Symbol.for() 全局注册

Symbol.for() 会在全局 Symbol 注册表中查找,如果存在相同 key 的 Symbol 就返回它,否则创建新的

const globalSym1 = Symbol.for("app.id");
const globalSym2 = Symbol.for("app.id");
console.log(globalSym1 === globalSym2); // true,它们是同一个Symbol
// 获取Symbol的key
console.log(Symbol.keyFor(globalSym1)); // "app.id"
// 普通Symbol没有key
const localSym = Symbol("local");
console.log(Symbol.keyFor(localSym)); // undefined

Symbol 的特性

唯一性

每个 Symbol 都是独一无二的

const sym1 = Symbol("test");
const sym2 = Symbol("test");
const sym3 = Symbol.for("test");
const sym4 = Symbol.for("test");
console.log(sym1 === sym2); // false,普通Symbol总是唯一的
console.log(sym3 === sym4); // true,全局Symbol相同key返回同一个

不可变性

Symbol 一旦创建就不能修改

const sym = Symbol("original");
// 没有方法可以修改Symbol的值或描述

不参与自动类型转换

const sym = Symbol("test");
console.log(String(sym)); // "Symbol(test)",需要显式转换
console.log(sym.toString()); // "Symbol(test)"
// 这些会报错
// console.log(sym + ""); // TypeError
// console.log(+sym); // TypeError

Symbol 作为对象属性

基本用法

const sym = Symbol("property");
const obj = {};
// 三种设置Symbol属性的方式
obj[sym] = "value1";
obj[Symbol("key2")] = "value2";
const obj2 = {
[Symbol("key3")]: "value3",
};

不可枚举性

Symbol 属性不会出现在常规的对象遍历中

const visibleProp = "visible";
const hiddenProp = Symbol("hidden");
const obj = {
[visibleProp]: "可以看到",
[hiddenProp]: "隐藏属性",
};
// 常规遍历看不到Symbol属性
for (let key in obj) {
console.log(key); // 只输出 "visible"
}
console.log(Object.keys(obj)); // ["visible"]
console.log(Object.getOwnPropertyNames(obj)); // ["visible"]
// 需要专门的方法获取Symbol属性
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(hidden)]
console.log(Reflect.ownKeys(obj)); // ["visible", Symbol(hidden)]

内置 Symbol

JavaScript 提供了许多内置 Symbol,用于元编程和自定义对象行为:

Symbol.iterator

定义对象的默认迭代器

const iterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
}
return { done: true };
},
};
},
};
for (let item of iterable) {
console.log(item); // 1, 2, 3
}

Symbol.toStringTag

自定义对象的字符串描述

class CustomClass {
get [Symbol.toStringTag]() {
return "CustomClass";
}
}
const obj = new CustomClass();
console.log(obj.toString()); // "[object CustomClass]"

Symbol.hasInstance

自定义 instanceof 行为

class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
console.log({} instanceof MyArray); // false

实际应用场景

1. 避免属性名冲突

// 库代码
const LIBRARY_ID = Symbol("libraryId");
class LibraryClass {
constructor() {
this[LIBRARY_ID] = Math.random();
}
getLibraryId() {
return this[LIBRARY_ID];
}
}
// 用户代码不会意外覆盖库的内部属性
const instance = new LibraryClass();
instance.libraryId = "user value"; // 不会影响库的内部状态

2. 创建枚举值

const Colors = {
RED: Symbol("red"),
GREEN: Symbol("green"),
BLUE: Symbol("blue"),
};
function processColor(color) {
switch (color) {
case Colors.RED:
return "处理红色";
case Colors.GREEN:
return "处理绿色";
case Colors.BLUE:
return "处理蓝色";
default:
return "未知颜色";
}
}
console.log(processColor(Colors.RED)); // "处理红色"

3. 实现私有属性模拟

const _private = Symbol("private");
class User {
constructor(name, password) {
this.name = name;
this[_private] = { password }; // "私有"属性
}
checkPassword(pwd) {
return this[_private].password === pwd;
}
}
const user = new User("Kayn", "secret");
console.log(user.name); // "Kayn"
console.log(user.password); // undefined,无法直接访问
console.log(user.checkPassword("secret")); // true

注意事项和最佳实践

JSON 序列化问题

Symbol 属性不会被 JSON.stringify() 包含

const sym = Symbol("key");
const obj = {
name: "Kayn",
[sym]: "hidden value",
};
console.log(JSON.stringify(obj)); // '{"name":"Kayn"}',Symbol属性丢失

调试建议

总是为 Symbol 提供有意义的描述

// ✅ 好的做法
const userIdSymbol = Symbol("userId");
const configSymbol = Symbol("config");
// ❌ 不好的做法
const sym1 = Symbol();
const sym2 = Symbol();

性能考虑

虽然 Symbol 查找很快,但创建 Symbol 有一定开销

// ✅ 在模块顶层创建,复用Symbol
const SHARED_SYMBOL = Symbol("shared");
function processData(data) {
return data[SHARED_SYMBOL];
}
// ❌ 避免在循环中创建Symbol
function badExample(arr) {
arr.forEach((item) => {
const sym = Symbol("temp"); // 每次都创建新的Symbol
item[sym] = "value";
});
}

总结

特性描述
唯一性每个Symbol都是独一无二的
不可变创建后无法修改
不可枚举不出现在for…in、Object.keys()等中
不自动转换不参与隐式类型转换
全局注册Symbol.for()可创建全局共享的Symbol

Symbol 是现代 JavaScript 中一个强大的特性,主要用于创建唯一标识符、避免属性名冲突,以及实现元编程。正确使用 Symbol 可以让代码更加健壮和安全。