引言:
在现在开发中,尤其是面对大型项目、团队合作和复杂的程序时,确保对象属性键的唯一性成为了开发者面临的一项挑战。可以脑补一下,在一个拥有成千上万个对象和属性的项目中,如何避免命名冲突?如何保证每个属性都有其独一无二的身份?这些问题不仅关乎代码的健壮性和性能,更直接影响到项目的可维护性和扩展性。倘若是你,接手一个庞大的项目,你该如何去维护它,又不使它出错呢?
在 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)。它允许你在创建对象字面量时动态地确定属性名。
-
- 动态属性名:方括号内的表达式会在运行时被求值,并且其结果将用作属性名。在这个例子中,
name的值"xbk"将成为对象classmates的一个属性名。
- 动态属性名:方括号内的表达式会在运行时被求值,并且其结果将用作属性名。在这个例子中,
-
- 避免语法错误:如果不使用方括号直接写
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...in 或 Object.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())的结果中。这有助于保护这些属性不被意外访问或修改,增强了代码的安全性和封装性。