各位同学,今天咱们来聊聊 JavaScript 里一个有点神秘,但有时候又挺有用的家伙:Object.getOwnPropertySymbols()
。别被名字吓到,其实它就是用来“捞”出一个对象里那些用 Symbol 定义的属性的。
好,开始我们的讲座!
Symbol 是个啥?
在深入 Object.getOwnPropertySymbols()
之前,咱们先得搞清楚 Symbol 到底是个什么玩意儿。简单来说,Symbol 是一种新的原始数据类型(primitive data type),跟 string
、number
、boolean
这些是兄弟。
它最主要的特点就是:唯一且不可变。 每次你调用 Symbol()
,都会创建一个全新的、独一无二的值。就算你给它传一样的描述,它也不会跟之前的 Symbol 相等。
const sym1 = Symbol("描述1");
const sym2 = Symbol("描述1");
console.log(sym1 === sym2); // false,它们不一样!
Symbol 的用处
那为啥要有 Symbol 呢? 主要就是为了解决属性名冲突的问题。在大型项目中,尤其是有第三方库参与的时候,很容易出现属性名重复,导致意想不到的 bug。 Symbol 提供了一种创建独一无二属性名的方式,避免了这种冲突。
常见的用例包括:
- 定义私有属性:虽然 JS 没有真正的私有属性(ES2022 引入了
#
私有属性,但 Symbol 仍然有其用武之地),但可以用 Symbol 来模拟。 因为 Symbol 属性不容易被外部访问到(除非你知道这个 Symbol 的值)。 - 作为常量:定义一些内部使用的常量,保证它们不会被意外修改。
- 用作元数据:给对象添加一些额外的、不影响对象核心功能的元数据。
- 控制对象的某些行为:例如,
Symbol.iterator
可以控制对象的迭代行为。
Object.getOwnPropertySymbols()
的作用
现在我们知道了 Symbol 是什么,以及它可以用来干什么。 接下来就到今天的主角了:Object.getOwnPropertySymbols()
。
这个方法的作用很简单: 它返回一个数组,包含了指定对象自身(不继承)的所有 Symbol 属性名。
也就是说,如果你想知道一个对象有哪些用 Symbol 定义的属性,就可以用这个方法。
语法
Object.getOwnPropertySymbols(obj)
obj
:要查找 Symbol 属性的对象。
返回值
一个包含 Symbol 属性名的数组。 如果对象没有任何 Symbol 属性,则返回一个空数组。
代码示例
咱们来看一些例子,加深理解:
const obj = {
name: "张三",
age: 30,
[Symbol("id")]: 123,
[Symbol("address")]: "北京市",
};
const symbols = Object.getOwnPropertySymbols(obj);
console.log(symbols); // 输出类似: [Symbol(id), Symbol(address)]
console.log(obj[symbols[0]]); // 输出: 123 可以通过symbol访问值
在这个例子中,obj
有两个 Symbol 属性:Symbol("id")
和 Symbol("address")
。 Object.getOwnPropertySymbols(obj)
返回一个包含这两个 Symbol 的数组。
和其他获取属性的方法对比
你可能会想,JS 里获取对象属性的方法挺多的,为什么要用 Object.getOwnPropertySymbols()
呢? 它和其他方法有什么区别?
咱们来对比一下:
Object.keys(obj)
: 返回一个包含对象自身所有可枚举的字符串属性名的数组。 不包括 Symbol 属性。Object.getOwnPropertyNames(obj)
: 返回一个包含对象自身所有字符串属性名的数组,包括不可枚举的。 不包括 Symbol 属性。for...in
循环: 遍历对象自身以及原型链上所有可枚举的字符串属性名。 不包括 Symbol 属性。Reflect.ownKeys(obj)
: 返回一个数组,包含对象自身所有属性名,包括字符串属性名和 Symbol 属性名,无论是否可枚举。
用表格总结一下:
方法 | 返回值 | 是否包含 Symbol 属性 | 是否包含不可枚举属性 | 是否包含原型链上的属性 |
---|---|---|---|---|
Object.keys(obj) |
可枚举的字符串属性名数组 | 否 | 否 | 否 |
Object.getOwnPropertyNames(obj) |
所有字符串属性名数组 | 否 | 是 | 否 |
for...in 循环 |
可枚举的字符串属性名(自身和原型链) | 否 | 否 | 是 |
Reflect.ownKeys(obj) |
所有属性名(字符串和 Symbol,自身) | 是 | 是 | 否 |
Object.getOwnPropertySymbols(obj) |
所有 Symbol 属性名(自身) | 是 | 是 (所有Symbol 属性默认不可枚举) | 否 |
可以看到,只有 Reflect.ownKeys()
和 Object.getOwnPropertySymbols()
能够获取 Symbol 属性。 但 Reflect.ownKeys()
会把字符串属性名也一起返回,而 Object.getOwnPropertySymbols()
只返回 Symbol 属性名。
应用场景举例
- 隐藏内部属性
const myObj = {
name: "示例对象",
[Symbol("internalData")]: { version: "1.0" }, // 内部版本信息
getVersion: function() {
const internalDataSymbol = Object.getOwnPropertySymbols(this).find(sym => sym.description === "internalData");
return this[internalDataSymbol]?.version;
}
};
console.log(myObj.name); // 输出:示例对象
console.log(myObj[Symbol("internalData")]); // 输出:undefined,因为每次Symbol()都是新的
console.log(myObj.getVersion()); // 输出: 1.0
const symbols = Object.getOwnPropertySymbols(myObj);
console.log(symbols); // 输出: [Symbol(internalData)]
console.log(myObj[symbols[0]]); // 输出: { version: "1.0" }
在这个例子中,Symbol("internalData")
用于存储对象的内部版本信息。 外部代码无法直接访问到这个属性,只能通过 getVersion()
方法来获取。 这样可以防止外部代码意外修改内部数据。
- 与第三方库集成
假设你使用了一个第三方库,它定义了一些 Symbol 常量,用于控制对象的行为。 你可以使用 Object.getOwnPropertySymbols()
来查找这些 Symbol 常量,并根据需要修改对象的行为。
// 假设这是第三方库的代码
const LIBRARY_CONSTANT = Symbol("library.constant");
const myObject = {
[LIBRARY_CONSTANT]: "默认值"
};
// 你的代码
const symbols = Object.getOwnPropertySymbols(myObject);
const librarySymbol = symbols.find(sym => sym.description === "library.constant");
if (librarySymbol) {
myObject[librarySymbol] = "自定义值"; // 根据需要修改对象的行为
}
console.log(myObject[librarySymbol]); // 输出 "自定义值"
- 模拟私有属性 (ES6之前)
虽然ES2022已经有了#
声明私有属性,但在没有#
的时代,Symbol 也是一个模拟私有属性的方式。
class Counter {
constructor() {
this._count = 0; // 使用下划线仅仅是一种约定,并不是真正的私有
this._countSymbol = Symbol('count'); // 创建一个 Symbol 用于模拟私有属性
this[this._countSymbol] = 0;
}
increment() {
this._count++; // 仍然可以从外部访问和修改
this[this._countSymbol]++;
}
getCount() {
return this._count; // 返回的是公共属性
}
getSymbolCount() {
return this[this._countSymbol]; //只能通过symbol访问
}
// 外部虽然可以通过 getOwnPropertySymbols 获得 Symbol,但是很难猜到 Symbol 的描述
// 即使获得了 Symbol,也需要能够访问到对象才能修改,增加了修改的难度
}
const counter = new Counter();
counter.increment();
console.log(counter.getCount()); // 1
console.log(counter.getSymbolCount()); // 1
console.log(counter._count); // 1 可以直接访问和修改
//console.log(counter[Symbol('count')]); // undefined,因为Symbol('count') !== this._countSymbol
const symbols = Object.getOwnPropertySymbols(counter);
console.log(symbols);
注意事项
- Symbol 属性是不可枚举的,也就是说,它们不会出现在
for...in
循环中,也不会被JSON.stringify()
序列化。 Object.getOwnPropertySymbols()
只返回对象自身的 Symbol 属性,不包括原型链上的。- Symbol 的
description
只是一个描述,用于调试,并不能保证唯一性。 两个不同的 Symbol 可以有相同的description
。 - Symbol 主要用于解决属性名冲突,而不是为了实现真正的私有属性。 即使使用 Symbol,外部代码仍然可以通过
Object.getOwnPropertySymbols()
访问到这些属性。
总结
Object.getOwnPropertySymbols()
是一个用于获取对象 Symbol 属性的工具。 它可以帮助我们:
- 查找对象中用 Symbol 定义的属性。
- 与第三方库集成,修改对象的行为。
- 模拟私有属性(虽然不是真正的私有)。
虽然 Symbol 属性不像字符串属性那么常用,但在某些场景下,它们可以发挥重要的作用。 掌握 Object.getOwnPropertySymbols()
可以让我们更好地理解和使用 Symbol,编写更健壮、更灵活的代码。
希望今天的讲座对大家有所帮助!下次再见!