ES5 的对象属性名都是字符串,这容易造成属性名的冲突。于是 ES6
中引入了一种新的 原始数据类型
—— Symbol
,表示独一无二的值。
至此 JavaScript
共有了 7 种数据类型:
typeof 的特殊值:
- typeof Symbol() = ‘symbol’
- typeof null = ‘object’
- typeof function(){} = ‘function’
Symbol值通过 Symbol()
函数生成。注意: Symbol()
函数前不能加 new
命令,因为 Symbol
类型是原始数据类型,不是对象。
let s = Symbol()
console.log(typeof s) // "symbol"
Symbol
值是独一无二的,所以Symbol()
函数支持传入字符串参数作为对该Symbol值的描述,用来区分。
let sym1 = Symbol('foo')
let sym2 = Symbol('bar')
console.log(sym1) // Symbol(foo)
console.log(sym2) // Symbol(bar)
console.log(sym1.toString()) // "Symbol(foo)"
console.log(sym1.toString()) // "Symbol(bar)"
注意: Symbol()
函数的参数只表示对当前 Symbol
值的描述,因此即使是相同的参数,生成的 Symbol
值也是不同的。
let s1 = Symbol()
let s2 = Symbol()
console.log(s1 === s2) // false
let s3 = Symbol('foo')
let s4 = Symbol('foo')
console.log(s3 === s4) // false
Symbol值可以转换为字符串或布尔值,不能转换为数值,并且不能直接与其他类型的值进行运算,必须显式转换为字符串后进行计算:
let sym = Symbol('sym')
// 可以转换为字符串或布尔值
String(sym) // "Symbol(sym)"
sym.toString() // "Symbol(sym)"
Boolean(sym) // true
!sym // false
// 不能转换为数值
Number(sym) // 错误
// 不能直接与其他类型运算,必须显示转换为字符串
'symbol is:' + sym // 错误
sym + 1 // 错误
'symbol is:' + sym.toString() // ok
String(sym) + 1 // ok
ES2019
提供了一个新的属性 description
直接返回 Symbol
值的描述:
const sym = Symbol('foo')
sym.description // "foo"
由于 Symbol
值是独一无二的,所以可以用来作为对象的属性名,保证属性名的唯一性,防止被意外修改或覆盖。
const sym_name = Symbol('sym_name')
const sym_foo = Symbol('sym_foo')
let obj = {
[sym_name]: 'symValue',
[sym_foo]() {
console.log('foo')
}
}
console.log(obj[sym_name]) // "symValue"
obj[sym_foo]() // "foo"
Symbol还可以用于定义一组常量,保证这组常量的值都不相等。
const COLOR_RED = Symbol()
const COLOR_GREEN = Symbol()
function getColor(color: symbol) {
switch (color) {
case COLOR_GREEN:
return 'green'
case COLOR_RED:
return 'red'
default:
throw new Error('unknown color')
}
}
Symbol
值作为属性名,无法被 for...of
, for...in
, Object.keys()
, Object.getOwnPropertyNames()
, JSON.stringify()
遍历:
const foo = Symbol('foo')
const obj = {} as any
obj[foo] = 'bar'
for (const key in obj) {
console.log(obj) // 无输出
}
console.log(Object.keys(obj)) // []
console.log(Object.getOwnPropertyNames(obj)) // []
console.log(JSON.stringify(obj)) // "{}"
但是 Symbol
属性名也不是私有属性,可以通过 Object.getOwnPropertySymbols()
方法获取指定对象的所有Symbol属性名:
console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol(foo) ]
有时想要重新使用同一个Symbol值,此时可以使用 Symbol.for()
方法。
Symbol.for(arg: string)
接受一个字符串参数,查找是否已存在该参数为名称的 Symbol
值,如果已存在就返回该 Symbol
值, 没有则以该参数为名称创建新的 Symbol
值,并将其注册到全局环境供之后查找。
注意: 使用
Symbol()
创建的值是无法被Symbol.for()
搜索到的,因为它没有被注册到全局环境。
const sym_1: symbol = Symbol('sym_1') // 没有注册到全局环境,除了 sym_1 变量之外无法获取该 Symbol值
const sym_2: symbol = Symbol.for('sym_2') // 没有查找到,新建一个并注册到全局环境
const sym_3: symbol = Symbol.for('sym_2') // 查找到了,返回该值
console.log(sym_1 === sym_2) // false
console.log(sym_2 === sym_3) // true
Symbol.keyFor()
能够返回一个已登记的 Symbol
值的 描述字符串
(description属性):
注意: 使用
Symbol()
创建的值是无法被Symbol.keyFor()
搜索到的,因为它没有被注册到全局环境。
const sym_1: symbol = Symbol('sym_1')
const sym_2: symbol = Symbol.for('sym_2')
const sym_3: symbol = Symbol.for('sym_2')
console.log(Symbol.keyFor(sym_1)) // undefined
console.log(Symbol.keyFor(sym_3)) // "sym_2"
ES6
提供了 11 个内置的 Symbol值,指向语言内部使用的方法。
当某个对象使用 instanceof
运算符判断是否为某个类的实例时,会调用该方法。例如: foo instanceof Foo
在语言内部实际调用的是 Foo[Symbol.hasInstanceof](foo)
:
class Foo {
static [Symbol.hasInstance](obj: any) {
return obj instanceof String
}
}
console.log(new String('123') instanceof Foo) // true
console.log([1, 2, 3] instanceof Foo) // false
指示该对象用于 Array.prototype.concat()
时是否可以展开。
// 数组, 默认展开
let arr: any = [1, 2]
console.log(arr[Symbol.isConcatSpreadable]) // undefined,数组默认可以展开
console.log([3, 4].concat(arr)) // [ 3, 4, 1, 2 ]
arr[Symbol.isConcatSpreadable] = false // 设置为false,不可以展开
console.log(arr[Symbol.isConcatSpreadable]) // false
console.log([3, 4].concat(arr)) // [ 3, 4, [ 1, 2, [Symbol(Symbol.isConcatSpreadable)]: false ] ]
// 类数组, 默认不展开
let arrLike: any = { 0: 'a', 1: 'b', length: 2 }
console.log(arrLike[Symbol.isConcatSpreadable]) // undefined, 类数组默认不展开
console.log([3, 4].concat(arrLike)) // [ 3, 4, { '0': 'a', '1': 'b', length: 2 } ]
arrLike[Symbol.isConcatSpreadable] = true // 设置为 true, 可以展开
console.log(arrLike[Symbol.isConcatSpreadable]) // true
console.log([3, 4].concat(arrLike)) // [ 3, 4, 'a', 'b' ]
Symbol.isConcatSpreadable
也可以定义在类里面:
class A1 extends Array {
constructor() {
super()
;(this as any)[Symbol.isConcatSpreadable] = true
}
}
class A2 extends Array {
constructor() {
super()
}
get [Symbol.isConcatSpreadable](): boolean {
return false
}
}
let a1 = new A1()
a1[0] = 'A'
a1[1] = 'B'
let a2 = new A2()
a2[0] = 3
a2[1] = 4
console.log([1, 2].concat(a1).concat(a2)) // [ 1, 2, 'A', 'B', [ 3, 4 ] ]
注意:
A1
中Symbol.isConcatSpreadable
属性设置在实例上,而A2
中设置在了类的原型上。
指向一个构造函数,创建衍生对象时使用该属性。
class MyArr<T> extends Array<T> {
}
let a = new MyArr(1, 2, 3)
let b = a.map(item => item)
let c = a.filter(item => item > 1)
console.log(b instanceof MyArr) // true
console.log(c instanceof MyArr) // true
上面例子中, a
是 MyArr
的实例,b
和 c
是衍生对象,你可能认为 b
和 c
都是数组方法生成的,应该是 Array
的实例,但是 b
和 c
是 MyArr
的实例。
Symbol.species
属性可以改变创建衍生对象使用的构造函数:
class MyArr<T> extends Array<T> {
static get [Symbol.species]() {
return Array
}
}
let a = new MyArr(1, 2, 3)
let b = a.map(item => item)
let c = a.filter(item => item > 1)
console.log(b instanceof MyArr) // false
console.log(c instanceof Array) // true
当调用 str.match(obj)
时使用,相当于 obj[Symbol.match](this)
。
class SymbolMatchDemo {
[Symbol.match](str: string) {
return str.indexOf('w')
}
}
let str = 'hello world'
(str as any).match(new SymbolMatchDemo()) // 6
当调用 str.replace(old, newStr)
时使用, 相当于 old[Symbol.replace](this, newStr)
。
class SymbolReplaceDemo {
[Symbol.replace](str: string, newStr: string) {
console.log(str, newStr)
}
}
let str = 'hello world'
(str as any).replace(new SymbolReplaceDemo(), 'xyz') // "hello world xyz"
当调用 str.search(obj)
时使用, 相当于 obj[Symbol.search](this)
。
class SymbolSearchDemo {
value: string = 'llo';
[Symbol.search](str: string) {
return str.indexOf(this.value)
}
}
let str = 'hello world'
console.log(str.search(new SymbolSearchDemo())) // 2
当调用 str.split(seperator, limit)
时使用,相当于 seperator[Symbol.split](this, limit)
。
class SymbolSplitDemo {
separator: string = 'ld';
[Symbol.split](str: string) {
let index: number = str.indexOf(this.separator)
if (index === -1) {
return str
} else {
return [
str.substring(0, index),
str.substring(index + this.separator.length)
]
}
}
}
console.log((ss as any).split(new SymbolSplitDemo())) // [ 'hello wor', '' ]
返回该对象的默认遍历器方法,用于 for...of
遍历时调用。
class SymbolIteratorDemo {
*[Symbol.iterator]() {
yield 'a'
yield 'b'
yield 'c'
}
}
let retArr = []
for (const item of new SymbolIteratorDemo()) {
retArr.push(item)
}
console.log(retArr) // [ 'a', 'b', 'c' ]
指定该对象被转为原始类型时返回的值。接受一个字符串参数表示当前运算模式,一共有三种模式:
class SymbolToPrimitiveDemo {
constructor(public numValue: number, public strValue: string) {}
[Symbol.toPrimitive](pattern: string) {
switch (pattern) {
case 'number':
return this.numValue
case 'string':
return this.strValue
case 'default':
return 'default'
default:
throw new Error('unknown pattern')
}
}
}
const stpd = new SymbolToPrimitiveDemo(123, 'abc')
2 * (stpd as any) // 246
2 + (stpd as any) // "2default"
'hello, ' + (stpd as any) // "hello, default"
String(stpd) // "abc"
(stpd as any) == 'default' // true
用于定制 toString()
方法返回的字符串中的对象的类型,例如 [object Array]
中的 Array
。
class SymbolToStringTagDemo {
get [Symbol.toStringTag]() {
return 'SymbolToStringTag'
}
}
console.log(new SymbolToStringTagDemo().toString()) // [object SymbolToStringTag]
指示使用 with
关键字时,该对象的哪些属性会被 with
环境排除。
严格模式无法使用
with
。
class SymbolUnscopablesDemo {
foo() {
return 'Symbol.unscopables'
}
bar() {
return 123
}
get [Symbol.unscopables]() {
return {
foo: true
}
}
}
const foo = () => 'outername'
const bar = () => 321
with (SymbolUnscopablesDemo.prototype) {
console.log(foo(), bar()) // "outername" 123
}