JS `Object.getOwnPropertySymbols()`:获取对象的 Symbol 属性

各位同学,今天咱们来聊聊 JavaScript 里一个有点神秘,但有时候又挺有用的家伙:Object.getOwnPropertySymbols()。别被名字吓到,其实它就是用来“捞”出一个对象里那些用 Symbol 定义的属性的。

好,开始我们的讲座!

Symbol 是个啥?

在深入 Object.getOwnPropertySymbols() 之前,咱们先得搞清楚 Symbol 到底是个什么玩意儿。简单来说,Symbol 是一种新的原始数据类型(primitive data type),跟 stringnumberboolean 这些是兄弟。

它最主要的特点就是:唯一且不可变。 每次你调用 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 属性名。

应用场景举例

  1. 隐藏内部属性
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() 方法来获取。 这样可以防止外部代码意外修改内部数据。

  1. 与第三方库集成

假设你使用了一个第三方库,它定义了一些 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]); // 输出 "自定义值"
  1. 模拟私有属性 (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,编写更健壮、更灵活的代码。

希望今天的讲座对大家有所帮助!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注