简介
为了解决 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]"
Symbol.unscopables
2019/12/11 补充
Symbol.unscopables 用于对象在使用 with 时在环境中排除的属性名称 该属性在严格模式('use strict')下无效
可设置的属性值
| 属性 | 默认值 |
|---|---|
| writable | true |
| enumerable | true |
| configurable | true |
该属性和 Object.defineProperty 设置的对象属性相同,不懂得可以去学习一下
var obj = {
foo: 1,
bar: 2
};
obj[Symbol.unscopables] = {
foo: false,
bar: true
};
with(obj) {
console.log(foo); // 1
console.log(bar); // ReferenceError: bar is not defined
}
该属性是为了解决老旧的代码中使用了ES6新增的方法作为变量名的函数,为了防止其出现错误的结果 例如:
var keys = [];
with(Array.prototype) {
keys.push("something");
}
由于 ES6 新增 Object.keys 方法,所以该函数会不会得到预期结果
参考文章