Symbol类型
Symbol类型
Symbol是ES6引入的原始数据类型,用于创建唯一的标识符。本文将介绍Symbol的创建、属性和常见用途,如定义对象的特殊方法和创建私有属性。
Symbol 基础
Symbol 是 JavaScript 的第七种原始数据类型,与 undefined
、null
、Boolean
、Number
、String
和 BigInt
并列。Symbol 的主要特点是唯一性,即使创建多个具有相同描述的 Symbol,它们也是不相等的。
创建 Symbol
使用 Symbol()
函数创建一个新的 Symbol:
// 创建一个没有描述的 Symbol
const sym1 = Symbol();
console.log(typeof sym1); // "symbol"
// 创建一个带描述的 Symbol
const sym2 = Symbol('description');
console.log(sym2.toString()); // "Symbol(description)"
// 每个 Symbol 都是唯一的
const sym3 = Symbol('description');
console.log(sym2 === sym3); // false
Symbol 的描述
Symbol 可以有一个可选的描述,主要用于调试目的:
const sym = Symbol('user id');
console.log(sym.description); // "user id"
Symbol 不能使用 new 操作符
Symbol 是原始值,不是对象,因此不能使用 new
操作符:
// 错误用法
try {
const sym = new Symbol(); // TypeError: Symbol is not a constructor
} catch (e) {
console.error(e.message);
}
Symbol 的特性
唯一性
Symbol 的主要特性是唯一性,即使描述相同,每个 Symbol 也是不同的:
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // false
不会被自动转换为字符串
与其他原始类型不同,Symbol 不会被自动转换为字符串:
const sym = Symbol('My symbol');
// 错误用法
try {
alert(sym); // TypeError: Cannot convert a Symbol value to a string
} catch (e) {
console.error(e.message);
}
// 正确用法
alert(sym.toString()); // "Symbol(My symbol)"
alert(sym.description); // "My symbol"
在对象字面量中使用 Symbol
Symbol 可以用作对象的属性键:
const id = Symbol('id');
const user = {
name: 'John',
[id]: 123 // 使用 Symbol 作为属性键
};
console.log(user[id]); // 123
Symbol 属性不会出现在常规的属性枚举中
Symbol 属性不会被 for...in
循环、Object.keys()
或 Object.getOwnPropertyNames()
返回:
const id = Symbol('id');
const user = {
name: 'John',
age: 30,
[id]: 123
};
// Symbol 属性不会出现在这些方法中
console.log(Object.keys(user)); // ["name", "age"]
console.log(Object.getOwnPropertyNames(user)); // ["name", "age"]
for (let key in user) {
console.log(key); // 只会输出 "name" 和 "age"
}
// 获取 Symbol 属性
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
// 获取所有属性,包括 Symbol
console.log(Reflect.ownKeys(user)); // ["name", "age", Symbol(id)]
全局 Symbol 注册表
有时我们希望具有相同描述的 Symbol 是相同的实体。为此,JavaScript 提供了全局 Symbol 注册表。
Symbol.for()
Symbol.for(key)
方法会在全局 Symbol 注册表中查找描述为 key
的 Symbol。如果找到,则返回它;否则,创建一个新的 Symbol 并将其添加到注册表中:
// 创建一个全局 Symbol
const globalSym1 = Symbol.for('global');
// 在其他地方获取相同的 Symbol
const globalSym2 = Symbol.for('global');
console.log(globalSym1 === globalSym2); // true
Symbol.keyFor()
Symbol.keyFor(sym)
方法返回全局 Symbol 注册表中 Symbol 的键:
const globalSym = Symbol.for('global');
console.log(Symbol.keyFor(globalSym)); // "global"
// 非全局 Symbol 返回 undefined
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined
内置 Symbol
JavaScript 内置了一些 Symbol,称为"众所周知的 Symbol"(well-known Symbols)。这些 Symbol 用于定制对象的行为。
Symbol.iterator
定义对象的默认迭代器:
const myIterable = {
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 };
} else {
return { done: true };
}
}
};
}
};
for (const item of myIterable) {
console.log(item); // 输出 1, 2, 3
}
Symbol.hasInstance
自定义 instanceof
操作符的行为:
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
Symbol.toPrimitive
定义对象转换为原始值的行为:
const user = {
name: 'John',
age: 30,
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return this.age;
}
if (hint === 'string') {
return this.name;
}
return this.name;
}
};
console.log(+user); // 30,数字转换
console.log(String(user)); // "John",字符串转换
console.log(user + ''); // "John",默认转换
其他内置 Symbol
JavaScript 还提供了其他内置 Symbol,如:
Symbol.toStringTag
:自定义Object.prototype.toString()
的返回值Symbol.species
:指定构造函数创建派生对象时使用的构造函数Symbol.match
、Symbol.replace
、Symbol.search
、Symbol.split
:自定义字符串方法的行为Symbol.isConcatSpreadable
:定义对象在Array.prototype.concat()
中是否展开
Symbol 的实际应用
创建私有属性
Symbol 可以用来创建对象的"私有"属性,这些属性不会被常规方法枚举:
const _id = Symbol('id');
const _password = Symbol('password');
class User {
constructor(name, password) {
this.name = name;
this[_password] = password;
this[_id] = Math.random();
}
checkPassword(password) {
return this[_password] === password;
}
get id() {
return this[_id];
}
}
const user = new User('John', '123456');
console.log(user.name); // "John"
console.log(user.id); // 随机生成的 ID
console.log(user.checkPassword('123456')); // true
// 私有属性不会被枚举
console.log(Object.keys(user)); // ["name"]
防止属性名冲突
当使用第三方库或在大型项目中工作时,Symbol 可以防止属性名冲突:
// 库 A
const libraryA = {
id: Symbol('id'),
setup() {
window[this.id] = 'Library A data';
}
};
// 库 B
const libraryB = {
id: Symbol('id'),
setup() {
window[this.id] = 'Library B data';
}
};
libraryA.setup();
libraryB.setup();
console.log(window[libraryA.id]); // "Library A data"
console.log(window[libraryB.id]); // "Library B data"
定义对象的特殊方法
使用内置 Symbol 可以定义对象的特殊行为:
class CustomCollection {
constructor() {
this.items = [];
}
add(item) {
this.items.push(item);
}
// 定义迭代器
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
} else {
return { done: true };
}
}
};
}
// 自定义 toString 行为
get [Symbol.toStringTag]() {
return 'CustomCollection';
}
}
const collection = new CustomCollection();
collection.add(1);
collection.add(2);
collection.add(3);
// 使用迭代器
for (const item of collection) {
console.log(item); // 输出 1, 2, 3
}
// 使用 toStringTag
console.log(Object.prototype.toString.call(collection)); // "[object CustomCollection]"
Symbol 的限制
虽然 Symbol 提供了一种创建"私有"属性的方法,但它们并不是真正的私有。以下方法仍然可以访问 Symbol 属性:
Object.getOwnPropertySymbols(obj)
:返回对象的所有 Symbol 属性Reflect.ownKeys(obj)
:返回对象的所有属性,包括 Symbol 属性
如果需要真正的私有属性,可以考虑使用 ES2022 引入的私有字段(#
前缀)或闭包。
总结
Symbol 是 JavaScript 中的一种原始数据类型,用于创建唯一的标识符。它的主要特点包括:
- 唯一性:每个 Symbol 都是唯一的,即使描述相同
- 不会被自动转换为字符串:需要显式调用
toString()
或访问description
属性 - 可以作为对象的属性键:使用
[symbol]
语法 - 不会出现在常规的属性枚举中:需要使用特殊方法访问
- 全局 Symbol 注册表:通过
Symbol.for()
和Symbol.keyFor()
访问 - 内置 Symbol:用于定制对象的行为
Symbol 在实际应用中主要用于创建私有属性、防止属性名冲突和定义对象的特殊方法。虽然它不提供真正的私有性,但在许多情况下已经足够使用。
随着 JavaScript 的发展,Symbol 已经成为语言中不可或缺的一部分,特别是在元编程和库开发中。