JS 中的绝对唯一性守护者:深入探索Symbol

908 阅读6分钟

引言:

在现在开发中,尤其是面对大型项目、团队合作和复杂的程序时,确保对象属性键的唯一性成为了开发者面临的一项挑战。可以脑补一下,在一个拥有成千上万个对象和属性的项目中,如何避免命名冲突?如何保证每个属性都有其独一无二的身份?这些问题不仅关乎代码的健壮性和性能,更直接影响到项目的可维护性和扩展性。倘若是你,接手一个庞大的项目,你该如何去维护它,又不使它出错呢?

在 ES6(ECMAScript 2015)中引入了一种全新的原始数据类型——Symbol。它就像一位隐形的守护者,默默地为每个属性提供了一个绝对唯一的标识符,确保了即使是最复杂的应用程序也能井然有序地运行。无论是在防止全局命名空间污染,还是在实现内部方法不被意外覆盖方面,Symbol 都扮演着不可或缺的角色。

对于数据类型简单的介绍:oi 哥们 来解锁JavaScript数据类型与内存奥秘:2024年最新指南前言: 在这个数字化飞速发展的时代,前端开发 - 掘金

应用场景:

比如在同学中的名字是不是可能出现重名的,如果没有symbol,重名问题不好解决。

案例1:

const classmates={
        // 字符串 同名 覆盖 
        "cy":1,
        "cy":2,
      
    }
console.log(classmates) 
//输出结果为: { kk: 2 }

我们可以看到,没有symbol的存在,我们添加进去,如果重名,就会被覆盖。

  • 字符串键覆盖:对于 "cy" 键,因为对象属性名必须是唯一的,所以后面的定义会覆盖前面的定义。因此,最终 classmates.cy 的值为 2

案例2:

const classmates={
    // 字符串 同名 覆盖 
    "cy":1,
    "cy":2,
    // symbol 可以代替 字符串作为key
    [Symbol('olivia')]:{ grade:60,age:18},
    [Symbol('olivia')]:{ grade:60,age:19}
}
console.log(classmates)
// 输出结果为:{
//  cy: 2,
//  [Symbol(olivia)]: { grade: 60, age: 18 },
//  [Symbol(olivia)]: { grade: 60, age: 19 }
// }

我们可以看到用上了symbol后,olivia就可以出现重名了,并没有发生覆盖。

  • 对于 Symbol 键,由于 [Symbol('olivia')] 和再次出现的 [Symbol('olivia')] 是两个独立的 Symbol 实例,它们并不会相互覆盖。这意味着 classmates 对象实际上会有三个不同的属性:一个字符串键 "cy" 和两个不同的 Symbol 键(即使它们都用了 'olivia' 作为标签)。

案例3:

对于[Symbol('olivia')]:{ grade:60,age:18} 这一处的[],想必大家有疑问?为什么要加[]?


const name ="xbk"
const classmates={   
    [name]:"猛男",
}
    console.log(classmates)
// 输出结果为:{ xbk: '猛男' }

当你使用变量或表达式作为对象属性名时,必须将这个变量或表达式放在方括号 [] 内。

这种语法称为计算属性名(computed property names)。它允许你在创建对象字面量时动态地确定属性名。

    1. 动态属性名:方括号内的表达式会在运行时被求值,并且其结果将用作属性名。在这个例子中,name 的值 "xbk" 将成为对象 classmates 的一个属性名。
    1. 避免语法错误:如果不使用方括号直接写 name: "猛男",JavaScript 会认为 name 是一个静态的标识符而不是一个变量,这会导致 classmates 对象有一个名为 "name" 的属性,而不是 "xbk"

补充:对于案例2的解释:

  • 动态属性名:在这个例子中,Symbol('olivia') 是一个函数调用,它返回一个新的 Symbol 实例。如果不使用方括号,JavaScript 会尝试将 Symbol('olivia') 解释为一个静态的属性名,这显然是不正确的。

深入讲讲:可枚举性:

代码案例:

 let name ="xbk"

    <!--  同学 对象字面量 -->
    const classmates={
        // 字符串 同名 覆盖 
        "cy":1,
        "cy":2,
        [name]:"猛男",
        // symbol 可以代替 字符串作为key
        [Symbol('Mark')]:{grade:50,age:10},
        [Symbol('olivia')]:{ grade:60,age:18},
        [Symbol('olivia')]:{ grade:60,age:18}
        console.log(Object.keys(classmates))
        console.log(Object.values(classmates))
        console.log(  Object.entries(classmates))
        // 输出结果为:
     //[ 'cy', 'xbk' ]
     //[ 2, '猛男' ]
     //[ [ 'cy', 2 ], [ 'xbk', '猛男' ] ]
    }

首先来讲讲Object.keys() 、Object.values()、Object.entries()

  • Object.keys() 对象的键名数组,但是不包括Symbol 类型的键名
  • Object.values() 对象的键值数组,但是不包括Symbol 类型的键值
  • Object.entries() 对象的键值对数组,但是不包括Symbol 类型的键值对

我们可以看到这些方法都不包含symbol,并且都是可以枚举的,可以使用 for...in进行输出

如:

const obj = { 
stringKey: 'value', 
[Symbol('symbolKey')]: 'symbolValue' };
for (let key in obj) 
{ console.log(key, obj[key]); }
// 输出: 
// stringKey value

那么如何访问 Symbol 键?

虽然 for...in 无法访问 Symbol 键,但是你可以使用其他方法来获取和操作这些键:

Object.getOwnPropertySymbols() :返回一个数组,包含指定对象自身的所有 Symbol 属性。用最上面的案例进行输出我们得到了:[ Symbol(Mark), Symbol(olivia), Symbol(olivia) ]

Object.getOwnPropertyDescriptors() :可以用来查看对象的所有属性描述符,包括 Symbol 键。

Symbol 就不能遍历了吗?

Symbol 键本身并不是不能遍历,而是因为它们默认是不可枚举的(enumerable: false),所以不会出现在使用 for...inObject.keys() 这样的常规遍历方法的结果中。然而,JavaScript 提供了专门的方法来遍历 Symbol 键。

使用 Object.getOwnPropertySymbols()

Object.getOwnPropertySymbols() 方法返回一个数组,包含指定对象自身的所有 Symbol 属性键。你可以结合这个方法与 for...of 循环或 .forEach() 来遍历 Symbol 键。

const obj = {
    stringKey: 'value',
    [Symbol('symbolKey')]: 'symbolValue'
};

const symbolKeys = Object.getOwnPropertySymbols(obj);

for (let sym of symbolKeys) {
    console.log(sym, obj[sym]);
}
// 输出:
// Symbol(symbolKey) symbolValue

使用 Object.getOwnPropertyDescriptors()

虽然 Object.getOwnPropertyDescriptors() 主要用于获取对象属性的描述符,但它也可以用来间接遍历 Symbol 键。你可以通过检查描述符中的 enumerable 属性来区分该属性是否可以枚举。

const obj = {
    stringKey: 'value',
    [Symbol('symbolKey')]: 'symbolValue'
};

const symbols = Object.getOwnPropertySymbols(obj);
symbols.forEach(symbol => {
    console.log(symbol, obj[symbol]);
});
// 输出:
// Symbol(symbolKey) symbolValue

总结:

唯一性

  • 每个 Symbol 实例都是独一无二的,即使使用相同的标签创建也不会发生冲突。这使得 Symbol 成为了定义私有属性或内部方法的理想选择,特别是在大型项目和团队协作中避免命名冲突。

动态属性名

  • 通过计算属性名语法 [expression],可以在创建对象时动态地确定属性名。这对于需要灵活性的应用场景非常有用,例如根据用户输入或外部数据源生成属性名。

不可枚举性

  • 默认情况下,Symbol 键是不可枚举的,这意味着它们不会出现在常规遍历方法(如 for...in 或 Object.keys())的结果中。这有助于保护这些属性不被意外访问或修改,增强了代码的安全性和封装性。