概述

Symbol是ES6新引入的原始数据类型,也是JavaScript语言的第七种基本类型

Undefined, Null, Boolean, String, Number, Object, Symbol

引入Symbol的目的是为了保证每个属性的名字“独一无二”,对象的属性名可以有两种类型:字符串和Symbol类型

Symbol值通过Symbol()生成

1
2
3
let s = Symbol();

typeof s // => symbol

由于Symbol值不是对象,故不能在Symbol()前添加new

1
2
3
let s1 = new Symbol();

// Uncaught TypeError: Symbol is not a constructor

Symbol函数可以接受一个字符串作为参数,表示对实例的描述

1
2
3
4
5
6
let s = Symbol('asd');

console.log(s); // => Symbol(asd)
console.log(typeof s); // => symbol
console.log(s.toString()); // => 'Symbol(asd)'
console.log(typeof s.toString()) // => string

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

1
2
3
4
5
6
7
8
const obj = {
toString: function () {
return 'asd';
}
};

let s = Symbol(obj);
console.log(s); // => Symbol(asd)

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

1
2
3
4
5
6
7
8
9
let s1 = Symbol();
let s2 = Symbol();
s1 == s2 // => false
s1 === s2 // => false
---------------------------------------------------------
let s1 = Symbol('asd');
let s2 = Symbol('asd');
s1 == s2 // => false
s1 === s2 // => false

Symbol不能与其他类型的值进行运算,但可以显示转换为字符串,也可以转换为布尔值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let s1 = Symbol('asd');
let s2 = 'qwe';

s1 + s2
// Uncaught TypeError: Cannot convert a Symbol value to a string

`${s1}`
// Uncaught TypeError: Cannot convert a Symbol value to a string

String(s1) // => 'Symbol(asd)'
s1.toString() // => 'Symbol(asd)'

!s1 // => false
Boolean(s1) // => true

作为属性名

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

Symbol作为属性名有三种写法

  • 第一种写法:

    1
    2
    3
    4
    5
    6
    let s = Symbol('asd');

    let obj = {};
    obj[s] = 'hello';

    console.log(obj[s]); // => hello
  • 第二种写法:

    1
    2
    3
    4
    5
    6
    7
    let s = Symbol('asd');

    let obj = {
    [s]: 'hello'
    };

    console.log(obj[s]); // => hello

    在对象内部使用Symbol定义属性时,Symbol值必须放在方括号中,原因是命名时会默认将属性名转换成字符串,不会读取作为标志名所指代的值,导致属性名实际上是一个字符串,而不是一个Symbol

    1
    2
    3
    4
    5
    6
    7
    8
    let s = Symbol('asd');

    let obj = {
    s: 'hello'
    };

    console.log(obj[s]); // => undefined
    console.log(obj['s']); // => hello
  • 第三种写法:

    1
    2
    3
    4
    5
    6
    let s = Symbol('asd');

    let obj = {};
    Object.defineProperty(obj, s, { value: 'hello' });

    console.log(obj[s]); // => hello

Symbol值作为对象属性名时不能使用点运算符,理由和必须加方括号相同

1
2
3
4
5
6
7
let s = Symbol('asd');

let obj = {};
obj.s = 'hello';

console.log(obj[s]); // => undefined
console.log(obj['s']); // => hello

属性名的遍历

Symbol作为属性名,不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()返回

1
2
3
4
5
6
7
8
9
10
11
12
13
let s = Symbol('asd');

let obj = {
[s]: 'hello',
'a': 'world'
};

for (let key in obj) {
console.log(key); // => 无输出
}

Object.keys(obj) // => ['a']
Object.getOwnPropertyNames(obj) // => ['a']

可以使用Object.getOwnPropertySymbols()获取指定对象的所有Symbol属性名

1
2
3
4
5
6
7
8
9
let s1 = Symbol('s1'),
s2 = Symbol('s2');

let obj = {
[s1]: 'hello',
[s2]: 'world'
};

Object.getOwnPropertySymbols(obj) // => [Symbol(s1), Symbol(s2)]

也可以使用Reflect.ownKeys返回所有类型的属性名

1
2
3
4
5
6
7
8
let s = Symbol('s');

let obj = {
[s]: 'hello',
'a': 'world'
};

Reflect.ownKeys(obj) // => ['a', Symbol(s)]

Symbol的内置方法

Symbol.for()

使用给定的key搜索现有的Symbol,如果找到则返回该Symbol;否则将使用给定的key在全局创建一个新的Symbol

1
2
3
4
let s1 = Symbol.for('s1');
let s2 = Symbol.for('s1');

s1 === s2 // => true

Symbol.keyFor()

从全局注册的Symbol中,为给定的Symbol返回对应的key

1
2
3
let s1 = Symbol.for('asd');

Symbol.keyFor(s1) // => asd

内置的Symbol值

  • Symbol.hasInstance

    对象的Symbol.hasInstance属性指向一个内部方法,对象使用instanceof运算符时会调用这个方法,判断该对象是否为某个构造函数的实例

    1
    2
    3
    4
    5
    6
    7
    8
    class Even {
    static [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0;
    }
    }

    1 instanceof Even // => false
    2 instanceof Even // => true

    上述代码中1 instanceof Even在语言内部实际调用的是Even[Symbol.hasInstance](1)

  • Symbol.isConcatSpreadable

    对象的Symbol.isConcatSpreadable属性是布尔类型,它可以控制数组或类似数组的对象的行为:

    1. 对于数组对象,默认情况下,用于Array.prototype.concat时,会按数组元素展开然后进行连接,重置Symbol.isConcatSpreadable可以改变默认行为

      1
      2
      3
      4
      5
      6
      7
      8
      9
      let arr1 = [1, 2], arr2 = [3, 4];

      arr1[Symbol.isConcatSpreadable] // => undefined
      arr2[Symbol.isConcatSpreadable] // => undefined

      [1, 2].concat(arr2, 5) // => [1, 2, 3, 4, 5]

      arr2[Symbol.isConcatSpreadable] = false;
      arr1.concat(arr2, 5) // => [1, 2, [3, 4], 5]
    2. 对于类似数组的对象,用于Array.prototype.concat时,该对象整体作为新数组的元素,重置Symbol.isConcatSpreadable可改变默认行为

      1
      2
      3
      4
      5
      6
      let obj = { 0: 'a', 1: 'b', length: 2 };

      [1, 2].concat(obj, 'c') // => [1, 2, obj, 'c']

      obj[Symbol.isConcatSpreadable] = true;
      [1, 2].concat(obj, 'c') // => [1, 2, 'a', 'b', 'c']
  • Symbol.species

  • Symbol.match

    对象的Symbol.match属性指向一个函数,当执行str.match(obj)时,如果该属性存在,会调用它返回该方法的返回值

    1
    2
    3
    String.prototype.match(regexp)
    // 相当于
    regexp[Symbol.match](this);
    1
    2
    3
    4
    5
    6
    let obj = {
    [Symbol.match](str) {
    return 'hello'.concat(str);
    }
    };
    'world'.match(obj) // => helloworld
  • Symbol.replace

    对象的Symbol.replace属性指向一个方法,当对象被String.prototype.replace方法调用时会返回该方法的返回值

    1
    2
    3
    4
    let x = {};
    x[Symbol.replace] = (...args) => console.log(args);

    'Hello'.replace(x, 'World') // => ['Hello', 'World']
  • Symbol.search

    对象的Symbol.search属性指向一个方法,当对象被String.prototype.search方法调用时会返回该方法的返回值

    1
    2
    3
    String.prototype.search(regexp)
    // 相当于
    regexp[Symbol.search](this)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class MySearch {
    constructor(value) {
    this.value = value;
    }
    [Symbol.search](str) {
    return console.log(str.indexOf(this.value));
    }
    }

    'asdfgh'.search(new MySearch('df')) // => 2
  • Symbol.split

    对象的Symbol.split属性指向一个方法,当对象被String.prototype.split方法调用时会返回该方法的返回值

    1
    2
    3
    String.prototype.split(separator, limit)
    // 相当于
    separator[Symbol.split](this, limit)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class MySplit {
    constructor(value) {
    this.value = value;
    }
    [Symbol.split](str) {
    let index = str.indexOf(this.value);
    if (index === -1) {
    console.log(str);
    } else {
    console.log([str.substr(0, index), str.substr(index + this.value.length)]);
    }
    }
    }

    'asdfgh'.split(new MySplit('asd')); // => ['', 'fgh']
    'asdfgh'.split(new MySplit('fgh')); // => ['asd', '']
    'asdfgh'.split(new MySplit('qwe')); // => asdfgh
  • Symbol.iterator

  • Symbol.toPrimitive

  • Symbol.toStringTag

  • Symbol.unscopables