简介
为了解决 ES5 之前对象属性名冲突, ES6 引入了新的原始类型(primitive) Symbol
他是JavaScript中的第七种数据类型,前六种分别是 null
undefined
Boolean
String
Number
Object
语法
Symbol([description])
复制代码
description 表示对 Symbol 描述,主要用于区分不同 Symbol 值
用法
Symbol 值通过 Symbol
函数生成,不需要使用 new
命令。这是因为Symbol
是一个原始类型的值,不属于对象,基本上,它是一种类似字符串的数据类型
let a = new Symbol()
// Uncaught TypeError: Symbol is not a constructor
let s = Symbol()
typeof s // "symbol"
复制代码
任意 Symbol 是不相等的
Symbol
函数的参数知识对当前值的描述,因此生成的 Symbol 值是不等的
let a = Symbol()
let b = Symbol()
a === b // false
let c = Symbol('foo')
let d = Symbol('foo')
c === d // false
复制代码
当然也有例外
Symbol.for()
可以做到,它接收字符串作为参数,然后全局搜索有没有以该名称命名的 Symbol 值,如果有,就返回,没有就创建一个全局的 Symbol 值
let a = Symbol.for('foo')
let b = Symbol.for('foo')
a === b // true
let c = Symbol('bar')
let d = Symbol.for('bar')
c === d // false
// 因为 c 是没有登记的值,所以 d 会在全局创建一个 Symbol('bar')
复制代码
Symbol.key()
因为登记的是全局环境,所以跨 iframe 或 service worker 也可以取到同一个值
iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);
iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
// true
复制代码
Symbol.keyFor()
Symbol.keyFor()
可以返回已登记的Symbol值的 description
let a = Symbol.for('foo')
Symbol.keyFor(a) // "foo"
let b = Symbol('bar')
Symbol.keyFor(b) // undefined
//因为 b 未进行登记
复制代码
Symbol 作为属性名
由于 Symbol 值都是不相等的,所以作为标识符,用于对象,可以防止同名属性覆盖
let a = Symbol()
let obj = {
a: 'string',
[a]: 'symbol' // 使用Symbol作为属性名 必须加上[]
}
obj.a // string
obj[a] // symbol
obj['a'] // string
obj[Symbol()] // undefined
let obj1 = {
[Symbol()]: 'Symbol1'
}
let obj2 = {
[Symbol()]: 'Symbol2'
}
let target = {}
Object.assign(target, obj1, obj2)
// { Symbol(): 'Symbol1', Symbol(): 'Symbol2' }
复制代码
那在访问的时候如何获取对象中的 Symbol 值呢? Object.getOwnPropertySymbols()
方法可以获取,该方法会返回对象中的所有 Symbol 信息,Object.getOwnPropertyDescriptors()
可以获得对象中 Symbol 值,以及对应的 value 信息
Object.getOwnPropertySymbols(target)
// [Symbol(), symbol()]
Object.getOwnPropertyDescroptors(target)
// [Symbol(): {...}, Symbol(): {...}]
复制代码
内置 Symbol 值
Symbol.hasInstance
Symbol.hasInstance
允许你重写 instanceof
运算符。当使用 instanceof
运算符时,会调用 Symbol.hasInstance
方法
class MyArr {
static [Symbol.hasInstance](data) {
if (data instanceof Array) {
return true
}
}
}
let data = []
data instanceof MyArr // true
复制代码
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable
用于 Array.prototype.concat()
用于被连接到数组,设置数组是否展开,默认 Symbol.isConcatSpreadable
为 underfined
。
数组是默认展开的
let arr = []
let arr1 = [1, 2]
arr.concat(1, 2, [arr1]) // [1, 2, 1, 2]
arr1[Symbol.isConcatSpreadable] = false
arr.concat(1, 2, [arr1]) // [1, 2, Array(2)]
复制代码
对象默认是不展开的
let arr = []
let obj = { '0': 2, '1': 2, 'length': 2 }
arr.concat(1, 2, obj) // [1, 2, {...}]
obj[Symbol.isConcatSpreadable] = true
arr.concat(1, 2, obj) // [1, 2, 1, 2]
复制代码
Symbol.species
Symbol.species
是一个非常机智的 Symbol,它指向了一个类的构造函数,这允许类能够创建属于自己的、某个方法的新版本
class MyArr extends Array {
static get [Symbol.species]() {
return Array
}
}
let a = new MyArr([1, 2, 3])
let b = a.map(x => x)
b instanceof MyArr // false
b instanceof Array // true
复制代码
ES5 中的 Array.prototype.map
实现可能是
Array.prototype.map = function(func) {
var newArr = new Array(this.length)
this.forEach(function (value, index, array) {
newArr[index] = func(value, index, array)
})
return newArr
}
复制代码
ES6 中的 Array.prototype.map
实现可能是
Array.prototype.map = function(func) {
let Species = this.constructor[Symbol.species]
let newArr = new Species(this.length)
this.forEach((value, index, array) => {
newArr[index] = func(value, index, array)
})
return newArr
}
复制代码
通过定义 Symbol.species
在调用 Array.prototype.map
时可以直接返回你想要的数据类型
Symbol.match
Symbol.match
允许你重写 String.prototype.match(myObj)
方法。当调用 String.prototype.match(myObj)
时,如果存在该属性,则会调用。
class MyObj {
constructor(value) {
this.value = value
}
[Symbol.match](str) {
if (str.includes(this.value)) {
return true
}
return false
}
}
let myObj = new MyObj('hello')
'hello world'.match(myObj) // true
'hell'.match(myObj) //false
复制代码
Symbol.replace
Symbol.replace
允许你重写 String.prototype.replace(myObj)
方法。当调用String.prototype.replace(myObj)
时,如果存在该属性,则会调用。
class MyObj {
constructor(value) {
this.value = value
}
[Symbol.replace](str, replacer) {
if (replacer === 'hello') {
return replacer
}
else {
return str
}
}
}
let myObj = new MyObj('hello')
'hello world'.replace(myObj, 'hello') // hello
'hello world'.replace(myObj, 'hel') // hello world
复制代码
Symbol.search
Symbol.search
允许你重写 String.prototype.search(myObj)
方法。当调用 String.prototype.search(myObj)
时,如果存在该属性,则会调用。
Symbol.split
Symbol.split
允许你重写 String.prototype.split(myObj)
方法。当调用String.prototype.split(myObj)
时,如果存在该属性,则会调用。
Symbol.iterator
ES6 中的 for of
循环会调用 Symbol.iterator
方法,可以通过 Symbol.iterator
来重写循环
class Collection {
*[Symbol.iterator]() {
let i = 0;
while(this[i] !== undefined) {
yield this[i];
++i;
}
}
}
let myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;
for(let value of myCollection) {
console.log(value);
}
// 1
// 2
复制代码
Symbol.toPrimitive
当当前对象被转为原始类型是会调用 Symbol.toPrimitive
方法,返回该对象相应的原始类型值
一共有三种模式
-
Number
-
String
-
Default
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
复制代码
Symbol.toStringTag
对象的 Symbol.toStringTag
属性,指向一个方法。在该对象上面调用Object.prototype.toString
方法时,如果这个属性存在,它的返回值会出现在toString
方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制 [object Array]
中 object
后面的那个字符串
// 例一
({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"
// 例二
class Collection {
get [Symbol.toStringTag]() {
return 'xxx';
}
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"
复制代码
参考文章