前言
在JavaScript的发展历程中,ES6引入了许多新的特性,其中的Symbol让我很是着迷,因为Symbol提供了一个新的原始数据(基本数据)类型,用于创建唯一的标识符,谁不喜欢专一的男人呢,一生只有一个唯一。这不仅让他特别适合用在对象属性键的应用上,避免了属性名冲突的问题。
简单介绍一下Symbol
前面我们已经说说了它是用来创建唯一的标识符,这是什么意思?意味着独一无二,绝无二家。我们来创建试一下.
创建Symbol
创建Symbol是通过调用 Symbol()
函数来实现的。Symbol()
是一个全局函数,用于生成一个新的、独一无二的 Symbol
值。它不是构造函数,因此不应该使用 new
操作符来调用它;如果这样做,会抛出一个错误。
const sym = Symbol();
console.log(sym) // Symbol()
给Symbol添加描述
我们还可以给Symbol添加描述(Description),虽然 Symbol
的值是唯一的,但你可以通过传递一个字符串作为参数来为 Symbol
添加一个描述性的标签(label)。这个标签不会影响 Symbol
的唯一性,主要用于调试和日志输出。
const sym = Symbol('我是唯一的');
console.log(sym); // Symbol(我是唯一的)
最主要的是Symbol的唯一性
每次调用 Symbol()
都会生成一个全新的、唯一的符号,即使传递相同的描述,生成的 Symbol
也是不同的。
const sym1 = Symbol('key');
const sym2 = Symbol('key');
console.log(sym1 === sym2); // false
为什么我们需要Symbol
在ES6之前,对象的属性名都是字符串,这非常容易造成属性名的冲突,举个例子,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式,即混入,区别于继承,js特有的),新方法的名字就有可能与现有方法产生冲突。如果有一种新的机制,可以保证每个属性的名字都是独一无二的,唯一的就可以从根本上去解决属性名冲突问题,这就是为什么需要Symbol的原因
来个场景带大家理解一下,你有一个来自第三方库的对象
thirdPartyObj
,并且你想给这个对象添加一个新的功能printInfo
。然而,你不希望破坏现有的功能,也不确定第三方库是否已经有了同名的方法。
不使用 Symbol 的情况
// 第三方提供的对象
let thirdPartyObj = {
printInfo: function() {
console.log('Third party info');
}
};
// 我们想混入自己的功能
thirdPartyObj.printInfo = function() {
console.log('Our own info');
};
// 调用 printInfo 方法
thirdPartyObj.printInfo(); // 输出 "Our own info"
在这个例子中,我们新添加的 printInfo
方法覆盖了原有的方法,这可能会破坏第三方库的预期行为。
使用 Symbol 的情况
// 第三方提供的对象
let thirdPartyObj = {
printInfo: function() {
console.log('Third party info');
}
};
// 创建一个独一无二的 Symbol
const printInfoSymbol = Symbol('printInfo');
// 混入自己的功能,使用 Symbol 作为属性名
thirdPartyObj[printInfoSymbol] = function() {
console.log('Our own info');
};
// 调用原有方法
thirdPartyObj.printInfo(); // 输出 "Third party info"
// 调用我们混入的方法
thirdPartyObj[printInfoSymbol](); // 输出 "Our own info"
在这个例子中,通过使用 Symbol
作为属性名,我们成功避免了与现有方法 printInfo
的冲突。原有的方法和新添加的方法都可以共存,不会互相干扰。
此外,由于 Symbol
是不可枚举的,除非你明确知道 Symbol
的引用,否则无法访问到这些属性,这也提供了一种形式上的私有化,有助于保护对象内部的状态不被外部代码轻易修改。
获取对象上的Symbol属性的门道
Symbol
作为对象属性键时,具有以下特点:
-
Object.keys()
、Object.values()
和Object.entries()
不包括Symbol
类型的键:这些方法只返回对象的可枚举属性,而Symbol
类型的键默认是不可枚举的,因此不会出现在这些方法的结果中。const obj = { [Symbol('id')]: 123, name: 'Alice' }; console.log(Object.keys(obj)); // ['name'] console.log(Object.values(obj)); // ['Alice'] console.log(Object.entries(obj)); // [['name', 'Alice']]
-
Object.getOwnPropertySymbols()
可以获取对象上的所有Symbol
键:如果你想访问对象上的Symbol
属性,可以使用Object.getOwnPropertySymbols()
方法。const obj = { [Symbol('id')]: 123, name: 'Alice' }; console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id)]
-
Reflect.ownKeys()
可以获取对象的所有键(包括Symbol
键) :如果你需要获取对象的所有键,包括Symbol
键和普通字符串键,可以使用Reflect.ownKeys()
方法。const obj = { [Symbol('id')]: 123, name: 'Alice' }; console.log(Reflect.ownKeys(obj)); // ['name', Symbol(id)]
Object.getOwnPropertyDescriptors()
可以查看对象的所有属性及其描述符: 虽然 symboles emeumberable 属性为true, 但是不可枚举.
const obj = {
[Symbol('id')]: 123,
name: 'Alice'
};
console.log(Object.getOwnPropertyDescriptors(obj));
Symbol 的动态属性名
在 JavaScript 中,属性名可以是静态的(如 obj.name
)或动态的(如 obj[variable]
)。Symbol
作为属性名时,必须使用方括号 []
语法来定义动态属性名。
const key = Symbol('id');
const obj = {
[key]: 123
};
console.log(obj[key]); // 123
为什么 [Symbol("olivia")]: { grade: 50, gender: "female" }
里面不能没有 []
?这是因为 Symbol
作为一种特殊的属性名,不能直接作为静态属性名使用。只有通过方括号 []
语法,才能将 Symbol
作为动态属性名赋值给对象。
- 静态属性名:指那些在编写代码时就已经明确指定的属性名
- 动态属性名:动态属性名 是指在运行时通过变量、表达式或其他计算结果来确定的对象属性名
Symbol.for()
创建Symbo除了调用函数Symbol() 可以创建,还可以使用Symbol.for() ,我们需要弄明白他们之间的区别
Symbol()
和 Symbol.for(key)
都是用于创建符号的工具,但它们的行为和适用场景有所不同。
-
Symbol()
:每次调用Symbol()
都会生成一个全新的、唯一的符号,即使传递相同的描述,生成的Symbol
也是不同的。这种符号是局部的,仅在当前作用域内有效。const sym1 = Symbol('key'); const sym2 = Symbol('key'); console.log(sym1 === sym2); // false
-
Symbol.for(key)
:Symbol.for(key)
会从全局符号注册表中查找是否存在与给定key
相关联的符号。如果存在,则返回该符号;如果不存在,则创建一个新的符号并将其存储在全局注册表中。因此,Symbol.for(key)
返回的符号是全局共享的,多次调用Symbol.for('key')
会返回同一个符号。const sym1 = Symbol.for('key'); const sym2 = Symbol.for('key'); console.log(sym1 === sym2); // true
-
适用场景:
- 使用
Symbol()
时,符号是局部的、唯一的,适合用于防止属性名冲突的场景。 - 使用
Symbol.for(key)
时,符号是全局共享的,适合用于跨模块或跨文件共享符号的场景。
- 使用
END
Symbol
是 ES6 引入的一个非常有用的新特性,它不仅提供了唯一的标识符,还解决了对象属性名冲突的问题。通过理解它,我们能在后面的项目开发中,更灵活的使用他,此外,Symbol
的不可枚举特性也使得它非常适合用于隐藏某些内部属性,避免不必要的暴露。