Symbol

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。
Symbol 值通过 Symbol 函数生成,也就是说,对象的属性名现在可以有两种类型:一种是字符串,另一种就是 Symbol 类型。只要属性名属于 Symbol 类型,就是独一无二的,可以保证不会与其他属性名冲突。

1
2
let s = Symbol()
typeof s //symbol

Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在 console 显示。

1
2
let s = Symbol("str")
s.toString() //'Symbol(str)'

如果 Symbol 的参数是一个对象,就会调用该对象的 toString 方法,再生成 Symbol 值

1
2
3
4
5
6
7
const obj = {
toString() {
return "test"
},
}
const s = Symbol(obj)
s.toString() //'Symbol(test)'

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

1
2
3
let s1 = Symbol()
let s2 = Symbol()
s1 === s2 //false

作为属性名的 Symbol

Symbol 值可以作为标识符用于对象的属性名,保证不会出现同名的属性,还能防止某一个键被不小心覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let symbol = Symbol()

//写法一
let a = {}
a[symbol] = "test"

//写法二
let a = {
[symbol]: "test",
}

//写法三
let a = {}
Object.defineProperty(a, symbol, { value: "test" })

实例:消除代码中的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getArea(shape, options) {
}
//转变后
const shaptType = {
triangle: Symbol();
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shaptType.triangle:
area = 0.5 _ options.width _ options.height;
break;
}
return area;
}

属性名的遍历

Symbol 属性不会出现在 for…in、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyName()返回。有一个 Object.getOwnPropertySymbols 方法可以获取指定对象的所有 Symbol 属性。

1
2
3
4
5
6
let obj = {}
let a = Symbol("hello")
let b = Symbol("world")
obj[a] = "a"
obj[b] = "b"
Object.getOwnPropertySymbols(obj) //[Symbol(hello), Symbol(world)]

Symbol.for()、Symbol.keyFor()

Symbol.for 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值,如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值。

1
2
3
let str1 = Symbol.for("test")
let str2 = Symbol.for("test")
str1 === str2 //true

Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key:

1
2
3
4
5
let s1 = Symbol.for("foo")
Symbol.keyFor(s1) //'foo'

let s2 = Symbol("foo")
Symbol.keyFor(s2) //undefined

Symbol.for 为 Symbol 登记的名字是全局环境的,可以在不同的 iframe 或 serviceWorker 中取到同一个值

内置的 Symbol 值

  • Symbol.hasInstance
    foo instanceof Foo 实际在内部调用的是 Foo[Symbol.hasInstance](foo):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class MyClass {
    [Symbol.hasInstance](foo) {
    return foo instanceof Array
    }
    }
    ;[1, 2, 3] instanceof new MyClass() //true

    class Even {
    static [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0
    }
    }
    1 instanceof Even //false
    2 instanceof Even //true
  • Symbol.isConcatSpreadable
    等于一个布尔值,表示该对象使用 Array.prototype.concat()时是否可以展开:

    1
    2
    3
    4
    5
    6
    7
    let arr1 = ["a", "b"]
    ;[1, 2].concat(arr1, "c") //[1, 2, 'a', 'b', 'c']
    arr1[Symbol.isConcatSpreadable] //undefined

    let arr2 = ["a", "b"]
    arr2[Symbol.isConcatSpreadable] = false
    ;[1, 2].concat(arr2, "c") //[1, 2, ['a', 'b'], 'c']

类似数组的对象也可以展开,默认值为 false,必须手动打开:

1
2
3
4
let obj = { length: 2, 0: "c", 1: "d" }
;["a", "b"].concat(obj) //['a', 'b', obj]
obj[Symbol.isConcatSpreadable] = true
;["a", "b"].concat(obj) //['a', 'b', 'c', 'd']

对一个类而言,Symbol.isConcatSprealable 属性必须写成实例属性:

1
2
3
4
5
6
class myClass extends Array {
constructor(args) {
super(args)
this[Symbol.isConcatSpreadable] = true
}
}
  • Symbol.species
    对象的 Symbol.species 属性指向当前对象的构造函数,创造实例时会默认调用这个方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class myClass extends Array {
    static get [Symbol.species]() {
    return Array
    }
    }
    let a = new myClass(1, 2, 3)
    let mapped = a.map((x) => x * x)
    a instanceof Array //true
    mapped instanceof Array //true
    a instanceof myClass //true
    mapped instanceof myClass //false

    //默认值等同于下面的写法
    // static get [Symbol.species]() { return this; }
  • Symbol.match
    对象的 Symbol.match 属性指向一个函数,当执行 str.match(myObject)时,如果该属性存在,会调用它的返回值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    String.prototype.match(regexp)
    //等同于
    regexp[Symbol.match](this)

    class myMatcher {
    [Symbol.match](string) {
    return "hello world".indexOf(string)
    }
    }
    e.match(new myMatcher()) //1
  • Symbol.replace
    对象的 Symbol.replace 属性指向一个方法,当对象被 String.prototype.replace 方法调用时会返回该方法的返回值:

    1
    2
    3
    String.prototype.replace(searchValue, replaceValue)
    //等同于
    searchValue[Symbol.replace](this, replaceValue)

    Symbol.replace 方法会收到两个参数,一个是 replace 方法正在作用的对象,第二个是替换后的值:

    1
    2
    3
    const x = {}
    x[Symbol.replace] = (...s) => console.log(s)
    "hello".replace(x, "world") //['hello', 'world']
  • Symbol.search
    对象的 Symbol.search 属性指向一个方法,当对象被 String.prototype.search 方法调用时会返回该方法的返回值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    String.prototype.search(regexp)
    //等同于
    regexp[Symbol.search](this)

    class mySearch {
    constructor(value) {
    this.value = value
    }
    [Symbol.search](string) {
    return string.indexOf(this.value)
    }
    }
    "foobar".search(new mySearch("foo")) //3
  • Symbol.split
    对象的 Symbol.split 属性指向一个方法,当对象被 String.prototype.split 方法调用时会返回该方法的返回值:

    1
    2
    3
    String.prototype.split(separator, limit)
    //等同于
    separator[Symbol.split](this, limit)

    重定义 split 方法的行为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class mySplit {
    constructor(value) {
    this.value = value;
    }
    [Symbol.split](string) {
    let index = string.indexOf(this.value);
    if (index === -1) return string;
    return [
    string.substr(0, index),
    string.substr(index + this.value.length);
    ];
    }
    }
    'foobar'.split(new mySplit('foo')); //['', 'bar']
    'foobar'.split(new mySplit('bar')); //['foo', '']
    'foobar'.split(new mySplit('1')); //'foobar'
  • Symbol.iterator
    对象的 Symbol.iterator 属性指向该对象的默认遍历器方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    let 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
    }
  • Symbol.toPrimitive

  • Symbol.toStringTag

  • Symbol.unscopables