第六种类型:Symbol

阅读 1.6k
标签: JavaScript

我们知道JavaScript有五种数据类型,分别是StringNumberBooleanNullUndefined。但从ES6开始,又多了两种数据类型,一个是Symbol,一个是BigInt。本篇就聊聊Symbol。

什么是Symbol

看这命名,应该是个构造函数吧?打印出来瞧瞧:


果真如此,其有两个方法,forkeyFor,还有一些属性,比如isConcatSpreadableiterator等。

简单使用

其语法很简单: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
最后编辑于: 2022-06-28

评论(0条)

(必填)
复制成功