Symbol让对象属性独一无二,再也不用担心“重名症”!

307 阅读5分钟

引言

在 JavaScript 中,Symbol 作为一种简单数据类型,它用于创建唯一的标识符。自 ES6 引入以来,Symbol 提供了一种新的方式来定义对象的属性键,这些键是独一无二的。这也是Symbol如此重要的原因,解决了当多个开发者或库在同一对象上添加属性时,可能会不小心使用了相同的属性名称,从而导致意外覆盖已有属性的情况发生的问题。由于每次调用 Symbol() 函数都会生成一个全新的、唯一的符号,因此可以确保不会出现两个完全相同的 Symbol 键,即使它们被赋予了相同的描述性标签。而本文就来详细介绍一下Symbol类型。

Symbol 的特性与基本用法

唯一性:

为了了解Symbol类型的唯一性和运用,我们不妨来看个例子:

// 为什么要有Symbol呢?(因为Symbol可以让你放心加入一些地方,绝对不会重复)
let name = "西西里"

// 同学 (对象字面量)
const classMates = {
    // key(字符串)会覆盖
    "xxl":1,
    "xxl":2,
    [name + "的传说"]:"猛男", 
    // Symbol 可以代替字符串作为key
    [Symbol("Mark")]:{grade: 50, gender: "male"},
    [Symbol("olivia")]:{grade: 80, gender: "female"},
    [Symbol("olivia")]:{grade: 80, gender: "female"},
}
console.log(classMates.xxl);
console.log(classMates[name]);
console.log(classMates);

在这里我们能看到输出结果为:

image.png

而输出这个结果的原因如下:

  • 我们在对象 classMates 中定义了两次 "xxl" 键。但是 JavaScript 对象不允许有重复的字符串键,因此第二个定义会覆盖第一个,所以当我们输出 classMates.xxl 的值得到的结果是 2,并且这是在开发中很可能出现的错误,那就是不小心使用了相同的属性名称。
  • [name] 是一个动态生成的键名,根据name的值来创建属性名,即 "西西里",在后续打印classMates时我们也可以看见西西里:‘猛男’这个结果。
  • [Symbol("Mark")] 和 [Symbol("olivia")] 分别创建了两个唯一的符号作为键名。即使我们创建两个 [Symbol("olivia")],但是由于每个 Symbol 都是唯一的,所以它们不会相互覆盖。这意味着我们在调用时不必担心冲突。

与 Object 方法的关系:

对于classMates这个对象,我们现在想对其进行遍历,希望能打印其中的内容,所以我们添加以下代码:

for(let [key, value] of Object.entries(classMates)){
    console.log(key, value);
}

// 或者用for...in...
for(const person in classMates){
   // person是字符串(key),classMates[person]是值(value)
   console.log(person, classMates[person]);
}

在这里我们通过Object.entries()调用classMates对象的键值对数组,并且使用for of来遍历输出,乍一看没有任何问题,但是当我们运行后,却得到了以下的结果

image.png

为什么Symbol类型的键值对没有输出呢?这是因为Symbol的设计初衷,Symbol 的主要目的是提供一种独一无二的标识符,以避免命名冲突。通常用来创建私有或半私有的属性,而这些属性不应该轻易被外部代码访问或修改。因此,Symbol 属性默认是不可枚举的(non-enumerable),这意味着它们不会出现在常规的属性遍历操作中。

但是要注意一点Symbol只是不会出现在常规的属性遍历,那么是不是有什么不常规的方法可以来调用Symbol的键值对呢?当然。

调用Symbol中的属性

对象属性描述符

那么就不得不介绍一下Object.getOwnPropertyDescriptors()方法了,调用后返回一个新对象,该对象的所有可枚举和不可枚举属性键都对应于传入对象的相应属性的描述符。当我们在上面的代码中加入以下代码后:

const syms = Object.getOwnPropertyDescriptors(classMates);
console.log(syms);

在输出结果中我们不仅可以查看属性值,还可以了解属性的行为特性,例如是否可以被修改、删除或枚举。

image.png

属性描述符中 Symbols 的表现

在属性描述符中,Symbol 类型的键一样被列出,并且它们同样拥有描述符对象来描述它们的特性。虽然 Symbol 键默认是不可枚举的 (即:enumerable: false),但是我们仍然能看到其属性。这是因为 Object.getOwnPropertyDescriptors() 不关心属性是否可枚举,而是全面地反映对象上所有自有属性的状态。

只获取所有 Symbol 类型的属性

如果我们值访问对象上的所有 Symbol 类型的属性,JavaScript 提供了专门的方法:

  • Object.getOwnPropertySymbols(obj):此方法返回一个数组,包含指定对象自身的所有 Symbol 属性键。这使得你可以明确地访问并操作这些符号键,即使它们是不可枚举的。

例如我们在上述代码中加入:

// 使用 Object.getOwnPropertySymbols 获取所有的 Symbol 键
const syms = Object.getOwnPropertySymbols(classMates);
console.log(syms);

// 结合使用以获取 Symbol 键及其对应的值
const data = syms.map(sym => classMates[sym]);
console.log(data);

输出结果就显而易见,内容只包含 Symbol 属性

image.png

即使 Symbol 键是不可枚举的,我们也可以通过这种方法安全地访问和操作它们。

小结:

Symbol 是 JavaScript 中用于创建唯一标识符的数据类型,避免对象属性命名冲突。默认不可枚举,增强了代码的安全性和封装性。通过 Object.getOwnPropertySymbols() 可访问所有 Symbol 属性。适用于定义私有属性和防止命名冲突,提升代码质量和可维护性,尤其在大型项目中表现突出。