ES6 引入了一种新的原始数据类型Symbol
,表示独一无二的值。
声明定义symbol的几种方式
// 第一种定义方式
let s = Symbol(); console.log(typeof s); // symbol
由于symbol的值是独一无二的,因此根据此特性,两个变量即使值是一样的,也不相等。
let s1 = Symbol(); let s2 = Symbol(); console.log(s1 === s2); // false
// 注意,Symbol
函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol
函数的返回值是不相等的。
let s = Symbol('hello');
let s1 = Symbol('hello');
console.log(s === s1); // false
给symbol添加参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。
注意:Symbol
函数前不能使用new
命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
// 第二种定义方式(登记机制)
let cms = Symbol.for();
console.log(cms); // Symbol(undefined)
let cms1 = Symbol.for("symbol");
console.log(cms1); // Symbol(symbol)
let cms2 = Symbol.for("symbol");
console.log(cms1 === cms2); //true
Symbol.for()
与Symbol()
这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()每次调用,返回同一个Symbol值。但是Symbol每次调用会返回不同的值。
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key
。
let s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" let s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
上面代码中,变量s2
属于未登记的 Symbol 值,所以返回undefined
。
ymbol.prototype.description
ES2019 提供了一个实例属性description
,直接返回 Symbol 的描述。
let s = Symbol("hello world"); // ES2019 console.log(s.description); // hello word
作为属性名的 Symbol
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
let mySymbol = Symbol(); let a = {} // 写法1: a[mySymbol] = "hello"; // 写法2 let a1 = { [mySymbol]: "hello" } console.log(a[mySymbol]); // hello console.log(a1[mySymbol]); // hello
注意,Symbol 值作为对象属性名时,不能用点运算符。
const mySymbol = Symbol(); const a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!"
上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol
作为标识名所指代的那个值,导致a
的属性名实际上是一个字符串,而不是一个 Symbol 值。
同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
let s = Symbol(); let obj = { [s]: function (arg) { ... } }; obj[s](123);
采用增强的对象写法,上面代码的obj
对象可以写得更简洁一些。
let obj = {
[s](arg) { ... }
};
属性名的遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
let a = Symbol("foo"); let b = Symbol("baz"); let obj = { c: "jacascript", d: "vue" } // 写法1: obj[a] = "html"; obj[b] = "css"; for (let item in obj) { console.log(obj[item]); }
// jacascript
// vue
有一个Object.getOwnPropertySymbols()
方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
let a = Symbol("foo"); let b = Symbol("baz"); let obj = { c: "jacascript", d: "vue" } // 写法1: obj[a] = "html"; obj[b] = "css"; for (let item in obj) { console.log(obj[item]); // jacascript vue } const mySymbol = Object.getOwnPropertySymbols(obj); console.log(mySymbol); // [ Symbol(foo), Symbol(baz) ]
另一个新的 API,Reflect.ownKeys()
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let a = Symbol("foo"); let b = Symbol("baz"); let obj = { c: "jacascript", d: "vue" } // 写法1: obj[a] = "html"; obj[b] = "css"; for (let item in obj) { console.log(obj[item]); // jacascript vue } const mySymbol = Object.getOwnPropertySymbols(obj); console.log(mySymbol); // [ Symbol(foo), Symbol(baz) ] const mySymbol1 = Reflect.ownKeys(obj); console.log(mySymbol1); // [ 'c', 'd', Symbol(foo), Symbol(baz) ]
上面代码,对象obj上定义了2个属性c、然后添加了2个Symbol属性。循环遍历的时候只打印了自己的属性。通过Object.getOwnPropertySymbols()方法返回得到所有的Symbol属性的数组。
通过Reflect.ownKeys();方法返回全部属性数组。
使用symbol解决字符串耦合的问题
例如:打印一个对象中两个同名人的分数
let user1 = "李四"; let user2 = "李四"; let grade = { [user1]: { js: 100, css: 89, html: 50 }, [user2]: { js: 90, css: 99, html: 70 }, } console.log(grade); // 李四: {js: 90, css: 99, html: 70}
上面代码,打印后我们会发现,前者会被后者覆盖掉。因为对象中两个key是耦合的。
解决方案:定义成Symbol方式
let user1 = { name: "李四", key: Symbol() }; let user2 = { name: "李四", key: Symbol() }; let grade = { [user1.key]: { js: 100, css: 89, html: 50 }, [user2.key]: { js: 90, css: 99, html: 70 }, } console.log(grade[user1.key]); // { js: 100, css: 89, html: 50 } console.log(grade[user2.key]); // { js: 90, css: 99, html: 70 }
注意:存取值的时候,需采用对象[]的方式把变量放入中括号中。否则会当成字符串。