ES6类型扩展-Symbol类型

ES5中包含5种原始类型:字符串、数字、布尔值、null和undefined。ES6引入了第6种原始类型 —— Symbol类型

为什么要引入Symbol?ES5中对象的属性名都是字符串,这很容易造成命名冲突,最常见的就是扩展第三库的时候,要添加的新方法很可能和已有方法产生冲突。Symbol可以保证每个属性的名字都是独一无二的,从根本上防止属性名冲突。

创建

Symbol值可以通过Symbol()函数创建。现在对象的属性名不仅可以是字符串,也可以是Symbol类型。凡是属性名是Symbol类型的,都是独一无二的。

let name = Symbol(), person = {}
person[name] = 'wmui'
console.log(person[name]) // wmui

注意: Symbol函数前不能使用new命令,否则会报错。因为生成的 Symbol 是一个原始类型的值,不是对象

Symbol函数接收一个可选参数,参数是一段文本,用来描述要创建的Symbol。描述仅用于方便代码的阅读和调试。

let name = Symbol('name'), person = {}
person[name] = 'wmui'
console.log(person[name]) // wmui
console.log(name) // Symbol(name)

Symbol描述被存储在内部的[[Description]]属性中,只有调用Symbol的toString()方法时才可以读取到这个属性。console.log(name) 会隐式调用name的toString()方法,但是不能直接在代码中访问[[Description]]

Symbol是原始值,ES6扩展了typeof操作符,所以可以用typeof来检测变量是否为symbol类型,如果是则返回"symbol"。

let name = Symbol('name')
console.log(typeof name) // symbol

使用

所有使用可计算属性名的地方,都可以使用Symbol。比如对象的属性名。

let name = Symbol('name')
let person = {
  [name]: 'wmui'
}

// 设置年龄
let age = Symbol('age')
Object.defineProperties(person, {
  [age]: {
    value: 10,
    writable: false
  }
})

console.log(person[name]) // wmui
console.log(person[age]) // 10

注意: Symbol值作为对象属性名时,不能用点运算符设置

let name = Symbol('name')
let person = {}
person.name = 'wmui'
console.log(person[name]) // undefined

共享体系

在大型应用程序中,很多时候需要跨文件共享同一个Symbol,ES6提供了一个可以随时访问的全局Symbol注册表。

Symbol.for()

Symbol.for()可以创建共享的Symbol,它接收一个参数,表示要创建的Symbol字符串标识符。这个参数同时也被用于Symbol的描述

let name = Symbol.for('name')
let name2 = Symbol.for('name')
let person = {}
person[name] = 'wmui'

console.log(name === name2) // true
console.log(person[name2]) // wmui
console.log(name2) // Symbol(name)

Symbol.for()方法首先在全局Symbol注册表中搜索键为name的Symbol是否存在。如果存在,直接返回已有的Symbol,否则,创建一个新的Symbol,并使用这个键在Symbol全局注册表中注册,随即返回新创建的Symbol

这个示例中,name和name2包含相同的Symbol,第一次调用Symbol.for()方法会创建这个Symbol,第二次调用可以直接从Symbol的全局注册表中检索到这个Symbol,所以他们是相等的

Symbol.keyFor()

Symbol.keyFor()方法可以在Symbol全局注册表中检索与Symbol有关的键

let name = Symbol.for('name')
let name2 = Symbol.for('name')
let name3 = Symbol('name')

console.log(Symbol.keyFor(name)) // name
console.log(Symbol.keyFor(name2)) // name
console.log(Symbol.keyFor(name3)) // undefined

name和name2都返回了name这个键,而在Symbol全局注册表中不存在name3这个Symbol,所以最终返回undefined

注意: Symbol.for()为Symbol值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值

类型转换

类型转换是JS中的一个重要语言特性,然而其他类型没有与Symbol逻辑等价的值

// 示例1
let name = Symbol('name') + ''
console.log(name) // 报错

// 示例2
let name = Symbol('name') / 1
console.log(name) // 报错

// 示例3
let name = Symbol('name')
console.log(!!name) // true

无论是尝试将Symbol和字符串拼接,还是和数字进行混合运算,均不能强制转换Symbol的类型,所以会报错。

注意: 布尔值除外,因为Symbol与JS中的非空值类似,其等价布尔值为true

属性检索

Symbol作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.getOwnPropertyNames()、Object.keys()、JSON.stringify()返回。于是,在ES6中添加了一个Object.getOwnpropertySymbols()方法来检索对象中的Symbol属性

Object.getOwnPropertySymbols()方法的返回值是一个包含所有Symbol自有属性的数组

let name = Symbol.for('name')
let person = {
  [name]: 'wmui'
}

let pro = Object.getOwnPropertySymbols(person)
console.log(pro.length) // 1
console.log(pro[0]) // Symbol(name)
console.log(person[pro[0]]) // wmui

另外一个API,Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名

let name = Symbol.for('name')
let person = {
  [name]: 'wmui',
  age: 10
}

console.log(Reflect.ownKeys(person)) // ["age", Symbol(name)] 09:25:57.327

内置Symbol

ES6提供了11个内置的Symbol值,指向语言内部使用的方法

Symbol.haslnstance

一个在执行instanceof时调用的内部方法,用于检测对象的继承信息

Symbol.isConcatSpreadable

一个布尔值,用于表示当传递一个集合作为Array.prototype.concat()方法的参数时,是否应该将集合内的元素规整到同一层级

Symbol.iterator

一个返回迭代器的方法

Symbol.match

一个在调用String.prototype.match()方法时调用的方法,用于比较字符串

Symbol.replace

一个在调用String.prototype.replace()方法时调用的方法,用于替换字符串的子串

Symbol.search

一个在调用String.prototype.search()方法时调用的方法,用于在字符串中定位子串

Symbol.species

用于创建派生类的构造函数

Symbol.split

一个在调用String.prototype.split()方法时调用的方法,用于分割字符串

Symbol.toprimitive

一个返回对象原始值的方法

Symbol.ToStringTag

一个在调用Object.prototype.toString()方法时使用的字符串,用于创建对象描述

Symbol.unscopables

一个定义了一些不可被with语句引用的对象属性名称的对象集合

这些方法平时用到的情况较少,Symbol.isConcatSpreadable可能会用到,这里介绍一下,其余不再详细介绍

对象的Symbol.isConcatSpreadable属性是一个布尔值,表示调用数组的concat()方法时,多维数组是否可以展开

// 示例1
let arr = ['a','b'], arr2 = ['c','d']
arr.concat(arr2,'e','f') // ["a", "b", "c", "d", "e", "f"]
console.log(arr[Symbol.isConcatSpreadable]) // undefined

// 示例2
let arr = ['a','b'], arr2 = ['c','d']
arr[Symbol.isConcatSpreadable] = true
arr.concat(arr2,'e','f') // ["a", "b", "c", "d", "e", "f"]
console.log(arr[Symbol.isConcatSpreadable]) // true

从示例可以看出,数组的默认行为是可以展开。Symbol.isConcatSpreadable属性等于undefined或true,都有这个效果

当Symbol.isConcatSpreadable属性默认为false,数组将不会展开

let arr = ['a','b'], arr2 = ['c','d']
arr[Symbol.isConcatSpreadable] = false
arr2[Symbol.isConcatSpreadable] = false
arr.concat(arr2,'e','f') // [["a", "b"], ["c", "d"], "e", "f"]
posted @ 2021-09-29 13:06  wmui  阅读(62)  评论(0编辑  收藏  举报