Symbol 是 ES6 引入的一种新的原始数据类型,表示唯一的、不可变的值,主要用作对象属性的键。
// 创建 Symbol
const sym1 = Symbol();
const sym2 = Symbol('description'); // 可选的描述字符串
console.log(typeof sym1); // "symbol"
console.log(sym1 === sym2); // false - 每个Symbol都是唯一的
const sym1 = Symbol('key');
const sym2 = Symbol('key');
console.log(sym1 === sym2); // false
console.log(sym1 === sym1); // true
// 即使描述相同,也是不同的Symbol
Symbol('foo') === Symbol('foo'); // false
const sym = Symbol('test');
// Symbol 值不能被修改
// 只能通过描述字符串来区分不同Symbol
const obj = {
[Symbol('key')]: 'value',
regularKey: 'regular'
};
console.log(Object.keys(obj)); // ['regularKey']
console.log(Object.getOwnPropertyNames(obj)); // ['regularKey']
// 需要使用专门的API获取Symbol属性
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(key)]
// 基本创建
const sym1 = Symbol();
const sym2 = Symbol('description');
console.log(sym1.toString()); // "Symbol()"
console.log(sym2.toString()); // "Symbol(description)"
// 从全局注册表创建或获取
const sym1 = Symbol.for('globalKey');
const sym2 = Symbol.for('globalKey');
console.log(sym1 === sym2); // true
// 获取已注册Symbol的描述
console.log(Symbol.keyFor(sym1)); // "globalKey"
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined - 不在全局注册表中
// 使对象可迭代
const iterableObj = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
for (let value of iterableObj) {
console.log(value); // 1, 2, 3
}
// 自定义对象的 toString 标签
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
const obj = new MyClass();
console.log(obj.toString()); // "[object MyClass]"
// 自定义 instanceof 行为
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
console.log({} instanceof MyArray); // false
// 指定派生对象的构造函数
class MyArray extends Array {
static get [Symbol.species]() {
return Array; // 返回父类Array,而不是MyArray
}
}
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
// 定义对象如何转换为原始值
const obj = {
value: 42,
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return this.value;
}
if (hint === 'string') {
return `Value: ${this.value}`;
}
return this.value; // 默认
}
};
console.log(+obj); // 42
console.log(`${obj}`); // "Value: 42"
const sym = Symbol('id');
const user = {
name: 'John',
age: 30,
[sym]: 12345 // 使用Symbol作为键
};
// 访问Symbol属性
console.log(user[sym]); // 12345
// 遍历时不会出现
for (let key in user) {
console.log(key); // name, age
}
console.log(JSON.stringify(user)); // {"name":"John","age":30}
const obj = {
[Symbol('a')]: 'a',
[Symbol('b')]: 'b',
regular: 'regular'
};
// 1. Object.getOwnPropertySymbols()
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a), Symbol(b)]
// 2. Reflect.ownKeys() - 获取所有键(包括Symbol)
console.log(Reflect.ownKeys(obj)); // ['regular', Symbol(a), Symbol(b)]
// 3. Object.getOwnPropertyDescriptors()
const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
// 库开发中避免污染用户对象
const LIBRARY_KEY = Symbol('libraryInternalKey');
function myLibrary(obj) {
obj[LIBRARY_KEY] = {
initialized: true,
cache: {}
};
// 内部逻辑使用Symbol属性
obj.doSomething = function() {
const internal = this[LIBRARY_KEY];
// 使用内部数据
return internal;
};
}
const User = (() => {
const _id = Symbol('id');
const _password = Symbol('password');
return class User {
constructor(name, password) {
this.name = name;
this[_id] = Math.random();
this[_password] = password;
}
getId() {
return this[_id];
}
checkPassword(pass) {
return this[_password] === pass;
}
};
})();
const user = new User('Alice', 'secret123');
console.log(user.name); // "Alice"
console.log(user.getId()); // 随机ID
console.log(user.checkPassword('secret123')); // true
// 无法直接访问Symbol属性
console.log(Object.keys(user)); // ["name"]
// 使用内置Symbol扩展对象功能
const logger = {
logLevel: Symbol.for('logLevel'),
[Symbol('log')](message, level = 'info') {
console.log(`[${level.toUpperCase()}] ${message}`);
}
};
// 自定义行为
logger[logger.logLevel] = 'debug';
const sym = Symbol('test');
console.log(String(sym)); // "Symbol(test)"
console.log(sym.toString()); // "Symbol(test)"
// Symbol不能转换为数字
console.log(Number(sym)); // TypeError: Cannot convert a Symbol value to a number
// 布尔值为true
console.log(Boolean(sym)); // true
// 错误写法
// const sym = new Symbol(); // TypeError: Symbol is not a constructor
// 正确写法
const sym = Symbol();
const obj = {
[Symbol('key')]: 'symbolValue',
key: 'stringValue'
};
// 字符串键可以通过多种方式访问
console.log(obj.key); // 'stringValue'
console.log(obj['key']); // 'stringValue'
// Symbol键只能通过Symbol本身访问
const sym = Symbol('key');
const obj2 = { [sym]: 'value' };
console.log(obj2[sym]); // 'value'
const Direction = Object.freeze({
UP: Symbol('UP'),
DOWN: Symbol('DOWN'),
LEFT: Symbol('LEFT'),
RIGHT: Symbol('RIGHT')
});
function move(direction) {
switch (direction) {
case Direction.UP:
console.log('Moving up');
break;
case Direction.DOWN:
console.log('Moving down');
break;
// ... 其他方向
}
}
move(Direction.UP);
// 使用全局Symbol在多个模块间共享
// module1.js
export const EVENT_SYMBOL = Symbol.for('app.events');
// module2.js
import { EVENT_SYMBOL } from './module1.js';
const eventBus = {
[EVENT_SYMBOL]: new Map(),
on(event, handler) {
if (!this[EVENT_SYMBOL].has(event)) {
this[EVENT_SYMBOL].set(event, []);
}
this[EVENT_SYMBOL].get(event).push(handler);
},
emit(event, data) {
if (this[EVENT_SYMBOL].has(event)) {
this[EVENT_SYMBOL].get(event).forEach(handler => handler(data));
}
}
};
Symbol 是 JavaScript 中强大而独特的特性:
唯一性:每个Symbol值都是唯一的,避免命名冲突 隐私性:Symbol属性默认不可枚举,适合实现私有属性 元编程:内置Symbol允许自定义对象行为 不可变性:Symbol值创建后不能被修改 全局注册表:通过Symbol.for()可以在不同上下文中共享Symbol
Symbol 特别适用于库开发、元编程和需要避免属性名冲突的场景,是 JavaScript 面向对象编程和元编程能力的重要补充。