引言
当你第一天去上班,愉快的打开项目的代码仓库,发现前辈留下的代码简直是“屎山”成百上千的变量,乱七八糟的命名,还没有注释!难道要删了重写!又或者你和别人合作写一个项目,在变量命名上难免会出现变量名设置成一样的了,难道说我们提前规定好你用什么我要什么?那要是一个大项目呢,程序员几十上百个?这要怎么搞!
这时候就到了我们今天的主角登场了Symbol
什么是Symbol?
Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值。它通常被用作对象属性的键,确保属性不会与其他键发生冲突。它是JavaScript中的第七种数据类型,与undefined、null、Number(数值)、String(字符串)、Boolean(布尔值)、Object(对象)并列。
你可以这样创建一个Symbol值:
const a = Symbol();
console.log(a); //Symbol()
使用Symbol函数可以生成一个Symbol类型的值,但是你不能在调用Symbol时使用new关键字,因为Symbol是基本数据类型,而不是对象。比如下面的写法是错误的:
//报错,Symbol is not a constructor
const a = new Symbol();
使用Symbol()创建一个Symbol类型的值并赋值给a变量后,你就得到了一个在内存中独一无二的值。现在除了通过变量a,任何人在任何作用域内都无法重新创建出这个值。例如当你这样写:
const b = Symbol();
运行运行 尽管a和b都是使用Symbol()创建出来的,但是它们在内存中看起来却是这样的:
每个
Symbol 值都是唯一的,即使传入相同的描述字符串,也不会相等。
const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 == sym2); // false
尽管sym1和sym2传入的字符串是一样的但是sym1和sym2依旧是执行两个不同的地址。
实际上,a变量拿到了内存中某块内存的唯一引用(这里所说的引用,其实就是该内存的地址)。如果不借助a变量,你不可能再得到这个地址。因此:
a != b; //a和b持有的是两块内存的引用
const c = a; //手动把a里保存的地址保存在c变量中
a == c; //c和a现在指向同一块内存,因为它们保存了同样的地址
这种行为看似难以理解,但其实它与对象遵循相同的规则,如:
var a = {};
var b = {};
a != b; //a和b各自被分配了不同的内存,因此它们保存了不同的地址
//借助变量a,变量c拿到了a指向的那个对象的地址,因此两者相等
var c = a;
a == c;
但是对于同为基本数据类型的字符串来说,它不遵循类似的规则。 比如:
var a = "123";
var b = "123";
a === b; //返回true。两者在常量区引用同一个字符串
我们首先通过变量a在内存中创建了字符串“123”,然后在不借助变量a的情况下,又通过var b = "123"拿到了对“123”这个字符串的引用,两者指向内存中的同一块内存地址。
因此我们说,a无法确保别的变量无法拿到它保存的地址(前提是不通过a)。但是对于var a = Symbol()这样的语句,a变量内保存的值是唯一的,因为除了借助a变量,你永远无法得到a中保存的值。这也是Symbol的本质
Symbol的作用
上面我们说到,Symbol只是标记一块内存,不能与其他数据类型进行运算,那么新增这样一种数据类型有什么用呢?
举个例子
let name="张三";
const classRoom ={
"cy":1,
"cy":2,
[name]:"猛男",
[Symbol('Mark')]:{grade: 50,gender: 'male'},
[Symbol('olivia')]:{grade: 80,gender:'female'},
[Symbol('olivia')]:{grade: 80,gender:'female'},
}
console.log(classRoom);
输出结果
cy:2
[Symbol(Mark)]:{grade: 50,gender: 'male'},
[Symbol(olivia)]:{grade: 80,gender:'female'},
[Symbol(olivia)]:{grade: 80,gender:'female'},
在上面的代码中,字符串类型的属性很容易就被覆盖了"cy"的输出结果就为2 而用symbol类型的变量作为键则不会被覆盖,就像 [Symbol('olivia')]:{grade: 80,gender:'female'}, 和[Symbol('olivia')]:{grade: 80,gender:'female'}, 虽然他们都用了"olivia"作为键名但是下一个并不会覆盖掉上一个。
symbol 的语法规范
1. 基本语法
上面介绍到,使用如下语法即可创建一个Symbol变量:
var a= Symbol();
由于Symbol不是继承自Object,因此不可以使用new关键字来生成Symbol变量。使用上述语句创建的变量a,在控制台中进行输出时会显示为Symbol()。假如有另一个变量:
var b = Symbol();
console.log(a);//Symbol()
console.log(b); //Symbol()
变量a和变量b并不是同一个值,但它们在控制台的输出却是一样的,这样不利于我们区分两个变量。为此,我们可以在调用Symbol的时候传入一个字符串作为对当前Symbol变量的描述:
var a = Symbol("11");
var b = Symbol("22");
console.log(s); //Symbol("11")
console.log(b); //Symbol("22")
如果你希望得到一个Symbol的描述符,可以借助Symbol原型上的description属性(Symbol.prototype.description):
const a= Symbol("symbol");
console.log(a.description); //symbol
Symbol还可以显式的转化为字符串或布尔值,但是不能转化为数值:
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym2 = Symbol();
Boolean(sym2) // true
2. Symbol属性的遍历
以Symbol类型的变量作为对象属性时,该属性不会出现在for … in、for … of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
const obj = {
[Symbol('a')]: 'valueA',
key1: 'value1'
};
console.log(Object.keys(obj)); // ['key1']
但该属性并不是私有属性,它可以被专门的Object.getOwnPropertySymbols()方法遍历出来。该方法返回一个数组,包含了当前对象的所有用作属性名的Symbol值
const obj = {
[Symbol('a')]: 'valueA',
[Symbol('b')]: 'valueB' };
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a), Symbol(b)]
另外,ES6新增的Reflect.ownKeys()方法可以遍历出所有的常规键名和Symbol键名。语法为:
const sym1 = Symbol('key1');
const sym2 = Symbol('key2');
const obj = {
[sym1]: 'value1',
[sym2]: 'value2',
normalKey: 'value3',
};
// 打印所有属性和值
Reflect.ownKeys(obj).forEach(key => {
console.log(key.toString(), obj[key]);
});
// 输出:
// normalKey value3
// Symbol(key1) value1
// Symbol(key2) value2
4. 总结
Symbol 提供了一种优雅的方式来创建唯一标识符,特别适合用于对象属性键,以避免命名冲突并增强代码的安全性和可维护性。在现代 JavaScript 开发中,特别是在构建复杂的应用程序或参与大规模协作项目时,合理运用 Symbol 可以显著提升代码质量和开发效率。