一、出现原因
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。为了解决这一问题,在ES6中便新增了一种新的原始数据类型Symbol
,它表示独一无二的值。前面我们说过JavaScript的六种数据类型分别是:
undefined, null, Boolean, String, Number, Object
加上现在说的Symbol,现在有七种了。Symbol实际上是一种唯一的标识符,可以作为对象的唯一属性名,这样就不会被覆盖了。
Symbol 值通过Symbol
函数生成,于是对象的属性名便有两种类型,一种是原来的字符串,另外就是现在说的Symbol类型。
let a = Symbol()
typeof a // symbol
console.log(a) // Symbol()
let b = Symbol('b')
typeof b // "symbol"
console.log(b) // Symbol(b)
注意:Symbol
函数前不能使用new
命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。实际上它是一种类似于字符串的数据类型。
二、特性
1、相同参数的Symbol函数返回的值是不相同的
let a1 = Symbol('a')
let a2 = Symbol('a')
console.log(a1 == a2,a1,a2) // false Symbol(a) Symbol(a)
这是因为Symbol
函数的参数只是对当前 Symbol 值的描述,因此相同参数的Symbol
函数的返回值是不相等的。
2、Symbol值不能与其他类型的数据进行运算,但是Symbol值可以显式转为字符串,Symbol值也可以转为布尔值
Symbol() + 1 // Uncaught TypeError: Cannot convert a Symbol value to a number
'this is ' + Symbol('a') // Uncaught TypeError: Cannot convert a Symbol value to a string
let str = Symbol('str')
console.log(String(str)) // "Symbol(str)"
Boolean(str) // true
3、Symbol的值作为对象的属性名,由于Symbol值的唯一性,因此不会覆盖属性名
let attr = Symbol()
// 第一种写法
let obj = {}
obj[attr] = 'Hello!'
console.log(obj,obj[attr]) // {Symbol(): "Hello!"}
// 第二种写法
let obj = { [attr] : 'hello!' }
console.log(obj,obj[attr]) // {Symbol(): "Hello!"}
// 第三种写法
let obj = {}
Object.defineProperty(obj, attr, {value: 'hello!'})
console.log(obj,obj[attr]) // {Symbol(): "Hello!"}
4、Symbol值作为对象属性名时,不能用点运算符
let attr = Symbol()
const obj = {}
obj.attr = 'hello!'
console.log(obj[attr], obj['attr']) // undefined "hello!"
因为点运算符后面总是字符串,所以不会读取attr作为标识名所指代的那个值,导致obj 的属性名实际上是一个字符串,而不是一个 Symbol 值。所以在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
let attr = Symbol()
let obj = {
[attr]: function(param){
console.log(param)
}
}
obj[attr](1234)
5、Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的
let greet = {}
greet.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
}
console.log(greet.levels.DEBUG, 'debug message'); // Symbol(debug) "debug message"
console.log(greet.levels.INFO, 'info message'); // Symbol(info) "info message"
注意Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。
三、Symbol 作为属性名的遍历
1、Object.getOwnPropertySymbols() 遍历属性名
当symbol作为属性名时,遍历属性名时,不能从for...in
、for...of
循环中取出,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
此时需要用Object.getOwnPropertySymbols()方法获取指定对象的所有 Symbol 属性名,该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
const attr1 = Symbol('attr1')
const attr2 = Symbol('attr2')
let obj = {
[attr1]:'hello1',
[attr2]:'hello2',
}
const res = Object.getOwnPropertySymbols(obj)
console.log(res) // [Symbol(attr1), Symbol(attr2)]
2、Reflect.ownKeys() 返回属性名
Reflect.ownKeys()
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = {
a:1,
b:[1,2],
[Symbol('c')]:'str'
}
Reflect.ownKeys(obj) // ["a", "b", Symbol(c)]
四、Symbol 相关函数使用
1、Symbol.for(),Sysmbol.keyFor()
当我们需要使用相同的Sysmbol值时,Symbol.for()就可以生成相同的值。它需要传一个字符串参数,然后他会先搜索是否存在以该参数作为名称的Symbol的值,如果有则返回这个Symbol的值,否则创建该字符串为名称的Symbol值并将其注册到全局。
let obj1 = Symbol.for('a')
let obj2 = Symbol.for('a')
console.log(obj1 === obj2) // true
Symbol.for()
与Symbol()
它们都会生成新的Symbol值,区别是Symbol.for()被登记在全局环境中供搜索而Symbol()则不会。
console.log(Symbol.for('a') === Symbol.for('a')) // true
console.log(Symbol('a') == Symbol('a')) // false
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key
let obj1 = Symbol.for('a')
console.log(Symbol.keyFor(obj1)) // a
let obj2 = Symbol('a')
console.log(Symbol.keyFor(obj2)) // undefined
Symbol.for()
为Symbol值登记的名字,是全局环境的,不管有没有在全局环境运行。