第六种类型:Symbol
我们知道JavaScript有五种数据类型,分别是String
、Number
、Boolean
、Null
、Undefined
。但从ES6开始,又多了两种数据类型,一个是Symbol
,一个是BigInt
。本篇就聊聊Symbol。
什么是Symbol
看这命名,应该是个构造函数
吧?打印出来瞧瞧:
果真如此,其有两个方法,for
和keyFor
,还有一些属性,比如isConcatSpreadable
,iterator
等。
简单使用
其语法很简单:Symbol([description])
。
const symbol1 = Symbol()
const symbol2 = Symbol(42)
const symbol3 = Symbol('foo')
上面定义了三个不同的symbol变量。请注意:由于Symbol是原始值
,所以不能使用new Symbol()的方式构建symbol(会报错)。
为什么需要Symbol
Symbol作为一种数据类型,最常用的是作为一个对象属性的标志符
,其属性的一大特征是不会随便被覆盖掉。
let firstName = Symbol('firstName')
let lastName = Symbol('lastName')
let person = {}
person[firstName] = 'Foo'
person[lastName] = 'Larry'
person.firstName = 'Bar'
console.log(person)
上面代码中,person对象一共有三个属性,其中有两个是symbol,一个是普通属性。
Symbol共享体系
当需要在整个代码库中创建一个全局的symbol类型时,需要使用 Symbol.for()
方法,Symbol.for方法会先在全局搜索相关键是否存在,存在则直接返回,不存在就创建。
如果需要全局检索与Symbol有关的键,需要使用Symbol.keyFor()
方法。
来看一个例子:
// Symbol.for: 会先在全局搜索相关键是否存在,存在则直接返回,不存在就创建
let uid = Symbol.for('uuid')
let uid2 = Symbol.for('uuid')
let uid3 = Symbol('uuid')
console.log(uid === uid2) // true
console.log(uid === uid3) // false
console.log(Symbol.keyFor(uid2)) // 'uuid'
console.log(Symbol.keyFor(uid3)) // undefined
上面代码中,首先用 Symbol.for('uuid')
创建了一个描述符为"uuid"的全局Symbol,当再次使用 Symbol.for('uuid') 时,因为全局中已经存在,所以直接返回该Symbol并赋值给uid2,所以变量 uid和uid2 是完全相等的。而uid3并非全局的Symbol,所以使用Symbol.keyFor(uid3)
时找不到,会返回undefined。
检索对象上的Symbols
对于对象上的普通属性,我们可以使用 Object.getOwnPropertyNames(obj)
来检索。
而对于对象上的Symbol属性,我们需要使用 Object.getOwnPropertySymbols(obj)
来检索。
let uid = Symbol.for('uuid')
let age = Symbol('const age')
let obj = {
[uid]: '123',
[age]: 100,
score: 580,
fullName: 'Larry Li',
}
let symbols = Object.getOwnPropertySymbols(obj)
let names = Object.getOwnPropertyNames(obj)
console.log(symbols)
console.log(names)
上面代码中,变量symbols是一个Symbol属性组成的数组,变量names则是普通属性组成的数组。
Symbol.isConcatSpreadable
这是Sumbol上的属性之一,类型为boolean,看名字就很好理解,就是控制在concat()时是否展开。当使用数组的concat方法时,会自动展开两个数组中的值来进行拼接。来看一段代码:
const alpha = ['a', 'b', 'c']
const numeric = [1, 2, 3]
let alphaNumeric = alpha.concat(numeric)
console.log(alphaNumeric) // Array ["a", "b", "c", 1, 2, 3]
numeric[Symbol.isConcatSpreadable] = false
alphaNumeric = alpha.concat(numeric)
console.log(alphaNumeric) // Array ["a", "b", "c", Array [1, 2, 3]]
默认情况下,数组中的Symbol.isConcatSpreadable
都是true,如果将其设置为false,则数组拼接时,将不会展开。
Symbol.iterator
这个属性是一个函数(生成器函数),为每一个对象定义默认的迭代器。
对于开发者自己创建的普通对象,是无法迭代的:
const myIterable = {
items: [3, 6, 9],
}
const result = [...myIterable]
用展开运算符时会直接报错,Uncaught TypeError: myIterable is not iterable
。
为了让其可以迭代,需要给它增加一个Symbol.iterator
属性,如下:
myIterable[Symbol.iterator] = function* () {
for (let item of this.items) {
yield item
}
}
这样,对象myIterable就变成了可迭代对象了,其迭代规则根据上面定义的方式来进行迭代。
小结
Symbol作为JavaScript中的第六个类型,因为其比较难以被意外覆盖而导致属性值改变,所以,它非常适合用于需要一定保护功能
的地方。
参考
- 深入理解ES6,Nicholas C. Zakas