ES6-Symbol

概述

Symbol是一种数据类型,代表第一无二的值,Symbol值通过Symbol函数生成,也就是说,对象的属性名现在可以是字符串类型,也可以是Symbol类型.

1
2
let s = Symbol()
console.log(typeof(s)) // symbol

Symbol函数前不能使用new命令,否则会报错,这是因为生成的Symbol是一个原始类型的值,不是对象.也就是说,Symbol不是对象,不能添加属性.也可以说Symbol是一种类似于字符串的数据类型.

1
2
3
4
var s1 = Symbol('foo')
var s2 = Symbol('bar')
console.log(s1, s1.toString())
console.log(s2, s2.toString())

如果Symbol的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个Symbol对象

1
2
3
4
5
6
7
8
9
10
var obj = {}
var s = Symbol(obj)
console.log(s) // Symbol([object Object])

var obj = {
toString() { return 'abc' }
}
var s = Symbol(obj)
console.log(s) // Symbol(abc)

Symbol函数的参数只表示对该Symbol的描述,因此相同参数的Symbol返回值是不相等的

1
2
3
var s1 = Symbol('a')
var s2 = Symbol('b')
console.log(s1 == s2) // false

Symbol不能与其他类型的值进行运算,会抛出TypeError

1
2
var s1 = Symbol('a')
console.log(s1 + "a") // Exception has occurred: TypeError: Cannot convert a Symbol value to a string

但是Symbol可以显式转换成字符串,也可以转换为布尔值

作为属性名的Symbol

由于每一个Symbol值都是不相等的,所以Symbol可以作为标识符用于对象的属性名,保证不会出现同名的属性

1
2
3
4
5
6
7
8
9
10
11
var mySymbol = Symbol()
// 第一种写法
var a = {}
a[mySymbol] = 'hello'
// 第二种写法
var a = {
[mySymbol]:'hellow'
}
// 第三种写法
var a = {}
Object.defineProperty(a, mySymbol, { value: 'hello' })

Symbol作为对象属性名时不能用点运算符

1
2
3
4
var mySymbol = Symbol()
var a = {}
a.mySymbol = 'hello'
console.log(a[mySymbol], a['mySymbol']) // undefined hello\

因为点运算符后总是字符串,所以不会读取mySymbol作为标识名所指代的值,所以a的属性名是一个字符串,而不是Symbol,同理,在对象内,使用Symbol定义属性时,Symbol必须放在方括号内.

1
2
3
4
5
var s = Symbol()
var a = {
[s]: () => { return 'hello' }
}
console.log(a[s]()) // hello

属性名的遍历

Symbol作为属性名,不会出现在for…in,for…of中,也不会被keys(),getOwnPropertyNames()返回,可以使用getOwnpropertySymbols方法获取指定对象的所有Symbol属性名

1
2
3
4
5
6
var obj = {}
var a = Symbol('a')
obj['b'] = 'b'
obj[a] = 'a'
console.log(Object.getOwnPropertyNames(obj)) // ['b']
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(a)]

使用Reflect.ownKeys可以获取到所有的键名,包括字符串和Symbol

1
2
3
4
5
var obj = {}
var a = Symbol('a')
obj['b'] = 'b'
obj[a] = 'a'
console.log(Reflect.ownKeys(obj)) // (2) ['b', Symbol(a)]

Symbol不可以用在for循环中.

Symbol.for(),Symbol.keyFor()

有时候希望使用同一个Symbol值,Symbol.for方法可以做到,它接收一个字符串为参数

1
2
3
var s1 = Symbol.for('foo')
var s2 = Symbol.for('foo')
console.log(s1 === s2) // true

Symbol.keyFor返回一个已登记的Symbol类型的key

1
2
3
4
var s1 = Symbol.for('foo')
console.log(Symbol.keyFor(s1)) // foo
var s2 = Symbol('foo')
console.log(Symbol.keyFor(s2)) //undefined

模块的Singleton模式

Singleton模式是指,调用一个类并且在认识时候都返回同一个实例

我们可以把实例放在顶层对象global中

1
2
3
4
5
6
7
8
// mod.js
function A() {
this.foo = 'hello'
}
if (!global._foo) {
global._foo = new A()
}
module.exports = global._foo
1
2
var a = require('./mod')
global._foo = 123

这样会使加载mod.js时global会失真,为了防止这种情况,我们可以使用Symbol

1
2
3
4
5
6
7
8
9
// mod.js
const FOO_KEY = Symbol.for('foo')
function A() {
this.foo = 'hello'
}
if (!global[FOO_KEY]) {
global[FOO_KEY] = new A()
}
module.exports = global[FOO_KEY]
1
2
3
4
module.exports = global[FOO_KEY]var a = require('./mod')
console.log(global[Symbol.for('foo')]) // A {foo: 'hello'}
global[Symbol.for('foo')] = 123
console.log(global[Symbol.for('foo')]) // 123

这样子可以避免global[FOO_KEY]无意间被覆盖,但是还是可以被改写

如果键名也使用Symbol生成,那外部就无法引用到这个值,也就无法被改写

1
const FOO_KEY = Symbol('foo')

这样子其他脚本都无法引用FOO_KEY