Symbol是ES6新增的数据类型,由Symbol函数生成,表示独一无二的值,属于简单数据类型,通常用于定义属性名,以保证不与其他属性名产生冲突
let s1 = Symbol('jack') // Symbol(jack)
let s2 = Symbol('jack') // Symbol(jack)
s1 === s2 // false,独一无二
创建Symbol时,传入的参数会成为它的description属性
const sym = Symbol('jack')
sym.description // jack
Symbol类型作为属性读写需要使用形如[symbol]的方式
let sym = Symbol()
let fn = Symbol('fn of Symbol')
let a = {
[sym]: 'Hello!',
[fn](){
alert(this[sym])
}
};
// 或者
let a = {}
Object.defineProperty(a, sym, { value: 'Hello!'})
访问时不能通过点运算符
let sym = Symbol()
let a = {}
a[sym] = 'jack'
a.sym = 'tom' // 点操作符读取的不是Symbol类型的sym,而是普通字符串sym
console.log(a[sym]) // jack
console.log(a.sym) // tom
通过Symbol类型可以定义一组常量,以保证唯一性
const COLOR_RED = Symbol()
const COLOR_GREEN = Symbol()
function getComplement(color) {
switch (color) {
case COLOR_RED: // 仅匹配唯一的COLOR_RED
return COLOR_GREEN
case COLOR_GREEN:
return COLOR_RED
default:
throw new Error('Undefined color');
}
}
Symbol属性只能通过Object.getOwnPropertySymbols()方法遍历,
不能被for-in、for-of、Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()遍历读取
const name = Symbol('name')
const obj = {
Symbol('id'): 1007
}
obj[name] = 'jack'
for(let i in obj){
console.log(i) // 无输出
}
let propertySymArr = Object.getOwnPropertySymbols(obj) // [Symbol(id), Symbol(name)]
for(let i in propertySymArr){
console.log(obj[propertySymArr[i]]) // 1007, jack
}
另外,Reflect.ownKeys(obj)可以返回包含常规键名和Symbol键名的数组
利用symbol属性名不能被常规方法遍历到的这一特性,可以将定义私有的属性
let size = Symbol('size')
class Collection {
constructor() {
this[size] = 0 // 私有
}
add(item) {
this[this[size]] = item // 妙啊~
this[size]++
}
static sizeOf(instance) {
return instance[size]
}
}
let x = new Collection()
x.add('foo')
Collection.sizeOf(x) // 1
x[0] // 'foo'
Collection.sizeOf(x) // 0
Object.keys(x) // ['0']
Object.getOwnPropertyNames(x) // ['0']
Object.getOwnPropertySymbols(x) // [Symbol(size)]
Reflect.ownKeys(x) // ['0',Symbol(size)]
Symbol.for(str)返回description为str的symbol变量,若没有则在全局注册一个
不多bb直接甩代码
let s1 = Symbol.for('foo') // 没有description属性为'foo'的symbol变量,全局注册一个 Symbol('foo')
let s2 = Symbol.for('foo') // 返回Symbol('foo')
s1 === s2 // true
Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key,若没有则返回undefined
let s1 = Symbol.for('foo') // 注意,这里将会在全局注册
Symbol.keyfor(s1) // foo
let s2 = Symbol('foo') // 没在全局注册
Symbol.keyfor(s2) // undefined
Singleton模式指的是调用一个类,任何时候返回的都是同一个实例,对于Node保证的是每次执行一个模块文件,返回的都是同一个实例,其核心思想是将实例放到顶层对象global作为属性存在
以下是不使用Symbol的情况:
// mod.js
function A(){
this.name = 'jack'
}
if(!global._a){
global._a = new A()
}
module.exports = global._a
// index1.js
const a = require('./mod.js')
// index2.js
const b = requre('./mod.js')
// a和b都应用同一个A类实例
问题在于,如果有个坏蛋在一个文件上修改了global._a,其他后面文件再引入global._a就GG了,那么可以用Symbol的唯一性和私用化属性这一特性了
const A_KEY = Symbol('a') // 局部注册,而非全局注册
function A() {
this.a = 'hello'
}
if (!global[A_KEY]) {
global[A_KEY] = new A()
}
module.exports = global[A_KEY]
// index3.js,写这个文件的小坏蛋想尝试修改global[A_KEY]
global[A_KEY] = {a: 'haha~'} // 抱歉,你没有这个[A_KEY]
global[Symbol('a')] = {a: '我再来'} //抱歉,symbol是唯一的,你这个Symbol('a') 与我的Symbol('a') 不是同一个
可以用于重写instanceof机制,当执行 instanceof 操作符时,会在构造器内部调用这个方法
a instanceof A// 实际调用A[Symbol.hasInstance](a)
举个例子
class Person{
[Symbol.hasInstance](obj){
return obj instanceof Array
}
}
[1,2,3] instanceof new Person // true
[Symbol.isConcatSpreadable]是一个布尔值,当指向 Array.prototype.concat()时,决定其是否可以展开
let arr1 = [1,2]
let res = [3,4].concat(arr1, 5) // [3,4,1,2,5]
let arr2 = [1,2]
arr2[Symbol.isConcatSpreadable] = false // 关闭展开机制
let res = [3,4].concat(arr2, 5) // [3,4,[1,2],5]
若想将一个伪数组的对象展开,可以指定[Symbol.isConcatSpreadable]为true
let obj = {length: 2, 0: 'c', 1: 'd'}
obj[Symbol.isConcatSpreadable] = true
['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
指向对象的默认遍历器方法
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1
yield 2
yield 3
};
[...myIterable] // [1, 2, 3]
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
}