什么是 Symbol, 它能做什么?
Symbol 是一种 ES6 新增的基本数据类型, 通过 Symbol 我们可以用来创建一个独一无二的值(这个值并不是字符串,表示唯一的标识符,可以理解为一个独一无二的东西);
怎么创建一个 symbol?打印这个 symbol 页面是怎么显示的?
可以使用 Symbol() 来创建这种类型的值
const name = Symbol()
console.log(name) // Symbol()
创建时, 我们可以给 symbol 一个描述, 也称为 symbol 名,这个在代码调试时非常有用
// 'Jimmy 不吃海鲜' 就是 name 这个symbol 的描述
const name = Symbol('Jimmy 不吃海鲜')
console.log(name) // Symbol(Jimmy 不吃海鲜)
Symbol 有什么特性?
每一个 symbol 都是唯一的,即使我们通过相同的描述创建的 symbol ;
const name1 = Symbol('Jimmy 不吃海鲜')
const name2 = Symbol('Jimmy 不吃海鲜')
console.log(name1 == name2) // false
也可以这么理解,这个 symbol 值就是一张纸,世界上没有两张相同的纸,但是你为了区分两张纸可以两张纸写上不同的名字。
注意:symbol 不会被自动转换为字符串
Javescript 中的大多数只都支持字符串的隐式转换。例如我们可以
alert任何值,但是symbol比较特殊, 它不支持被自动转换const name = Symbol('Jimmy 不吃海鲜') alert(name) // Uncaught TypeError: Cannot convert a Symbol value to a string这是一种防止混乱的“语言保护”;如果我们真的想要显示一个
symbol, 我们需要手动调用.toString()方法const name = Symbol('Jimmy 不吃海鲜') alert(name.toString()) // Symbol(Jimmy 不吃海鲜)如果想要获取
symbol的描述,可以通过symbol.description属性获取const name = Symbol('Jimmy 不吃海鲜') alert(name.description) // Jimmy 不吃海鲜
Symbol 有什么应用场景?
可做为对象的属性
根据规范, 对象的属性键可以有两种类型, 字符串类型和 symbol 类型
如果我们使用其他类型,例如数字, Boolean 类型, 它将会自动转换为字符串,所以 obj[1] 会转成 obj['1'], obj[true] 会转成 obj['true']
const id = Symbol('id')
const user = {}
user[id] = 9527
console.log(user[id]) // 9527
防止对象属性名冲突
symbol 允许我们创建对象的一个独一无问的属性,它在不知道对象原有属性名的情况下,扩展对象属性很有用。
例如, 如果我们使用的是属于第三方代码的 user 对象,我们想要给这个对象添加一些标识符;我们可以使用 symbol 键进行添加
const user = { // 属于第三方代码
name: 'Tony 爱吃鱼',
id: 9527
}
const id = Symbol('id')
user[id] = 666
alert(user[id]) // 666 说明我们可以使用 symbol 作为键来访问对象
// 注意: 如果要通过[]来访问值为 9527 的这个 id 属性, 需要加上引号
alert(user['id']) // 9527
使用 Symbol("id") 作为键,比起用字符串 'id' 有什么好处呢?
由于 user 对象属于另一个代码库,内部可能存在一个属性, 和我们想向其添加的属性是同名的, 如上面代码中的 id, 如果我们通过字符串 id 进行添加, 则会覆盖原有代码的字段,影响代码库中其他预定义的行为,但是通过 Symbol("id") 向其添加则不会,因为 symbol 是独一无二的, 只能通过创建的时候的这个句柄访问到, 而第三方是不会知道新定义的 symbol, 即使将 symbol 添加到 user 对象也是安全的
假设另外一段脚本也希望在 user 中有一段自己的标识符,以实现自己的目的
const id = Symbol('id')
user[id] = '另一段代码定义的 id 值'
虽然两个脚本都像 user 中添加了相同描述的 symbol, 但是两个标识符并不会冲突,因为 symbol 是独一无二的,即使有相同的描述
如果我们使用字符串的 id, 而不是 symbol, 那么就会出现冲突
const user = { name: 'Tony 爱吃鱼' }
// 我们的脚本使用了 id 属性
user.id = 9527
// 另外一端脚本也想讲 id 用于它的目的
user.id = 666 // 将会无意中重写了这个 id 值
对象字面量中的 symbol
如果我们在对象字面量中使用 symbol, 则只需要使用方括号括起来即可
const id = Symbol('id')
const user = {
name: 'Tony 爱吃鱼'
[id]: 9527
}
symbol 的访问隐藏
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。
但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。这些方法会过滤 symbol 类型数据。如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。
如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
Object.assign 会同时复制字符串和 symbol 属性
const id = Symbol('id')
const user = {
[id]: 9527
}
const clone = Object.assign({}, user)
alert( clone[id] ) // 123 symbol 属性也一起被复制到 clone 对象中了
全局 symbol
通常所有的 symbol 都是不同的,即使他们有相同的描述, 但是有的时候我们希望相同的名字具有相同的 symbol 实体, 例如在应用程序中想要访问的 symbo('id') 值的是完全相同的属性
我们可以使用 symbol.for 进行创建使用, symbol 会维护一个全局 symbol 注册表, 通过 symbol.for 创建使用的时候, 会先去注册表中读取描述符一样的 symbol, 有则直接返回, 没有则在创建,然后通过给定的描述符将其创建到注册表中
const id = Symbol.for('id') // 如果该 symbol 不存在,则创建它
// 再次读取
const idAgain = Symbol.for('id')
console.log( id === idAgain ) // true
Symbol.for 是按描述返回一个 symbor,如果想要通过全局 symbol 返回一个描述,则可以直接使用 Symbol.keyFor
const name = Symbol.for('Jimmy 不吃海鲜')
console.log(Symbol.keyFor(name)) // Jimmy 不吃海鲜
Symbol.keyFor 内部查找 symbol 键时, 是在全局 symbol 中查找的, 不会查找非全局 symbol, 所以如果 symbol 是非全局的, 因为无法找到会返回 undefined
const globalSymbol = Symbol.for('Jimmy 不吃海鲜')
const localSymbol = Symbol('Tony 爱吃鱼')
console.log(Symbol.keyFor(globalSymbol)) // Jimmy 不吃海鲜
console.log(Symbol.keyFor(localSymbol)) // undifined
最后
文中如有错误,欢迎在评论区指正,谢谢阅读