Symbol

1. 概述

Symbol 可以保证每个属性的名字都是独一无二的,从根本上防止属性名的冲突。

Symbol 是原始数据类型。

const s = Symbol();
console.log(typeof s); // 'symbol'

Symbol 函数前不能使用 new,否则会报错。

这是因为生成的 Symbol 是一个原始类型的值,不是对象,所以不能添加属性。它是一种类似于字符串的数据类型。

可以接受一个字符串作为参数,描述 Symbol 实例,便于区分。

const a = Symbol('a');
const b = Symbol('b');
console.log(a); // Symbol(a)
console.log(b); // Symbol(b)
console.log(a.toString()); // 'Symbol(a)'
console.log(b.toString()); // 'Symbol(b)'

如果字符串是对象,会先调用对象的 toString 方法,转成字符串再生成一个 Symbol 值。

console.log(Symbol({})); // Symbol([object Object]);

注:参数只是对实例的描述,相同参数的 Symbol 函数返回的值是不相等的。

const symbol1 = Symbol();
const symbol2 = Symbol();
console.log(symbol1 === symbol2); // false
const symbolA = Symbol('a');
const symbolB = Symbol('a');
console.log(symbolA === symbolB); // false

Symbol 值不能与其他类型的值进行运算,会报错。

const symbol = Symbol('my symbol');
console.log('Your name is ' + symbol); // Cannot convert a Symbol value to a string

可以显示地将 Symbol 值 转为字符串。

const symbol = Symbol('my symbol');
console.log(String(symbol)); // "Symbol(my symbol)"
console.log(symbol.toString()); // "Symbol(my symbol)"

也可以转为布尔值,但不能转为数值。

const symbol = Symbol('my symbol');
console.log(Boolean(symbol)); // true
console.log(!symbol); // false
console.log(Number(symbol)); // Cannot convert a Symbol value to a number

2. Symbol.prototype.description

const symbol = Symbol('my symbol');

上面的代码中,'my symbol' 就是 symbol 的 description。

读取这个描述,需要显示转为字符串:

console.log(String(symbol)); // "Symbol(my symbol)"

ES2019 提供了 description,可直接读取。

const symbol = Symbol('my symbol');
console.log(symbol.description); // 'my symbol'

3. 作为属性名的 Symbol

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

// 第一种
const obj = {};
obj[symbol] = 'Hello';
// 第二种
const obj = {
  [symbol]: 'Hello',
};
// 第三种
const obj = {};
Object.defineProperty(obj, symbol, {
  value: 'Hello',
});
console.log(obj[symbol]); // 'Hello'

Symbol 值作为属性名时不能用点运算符。

const obj = {};
obj.symbol = 'Hello'; // <-- 点运算符后面总是字符串,所以不会读取 symbol 作为标识名所指代的值
console.log(obj[symbol]); // undefined
console.log(obj['symbol']); // 'Hello'

4. 实例:消除魔术字符串

const getArea = shape => {
  switch (shape) {
    case 'triangle': // triangle 是魔术字符串,用到好多次
      console.log('triangle');
      break;
  }
};
getArea('triangle'); // 'triangle'
const shapeType = {
  triangle: Symbol('triangle'), // 用 Symbol 值代替属性值,防止冲突
};
const getArea = shape => {
  switch (shape) {
    case shapeType.triangle: // 用变量代替魔术字符串
      console.log(shape);
      break;
  }
};
getArea(shapeType.triangle); // Symbol(triangle)

5. 属性名的遍历

  • for...in 遍历自身、继承、可枚举的属性
  • Object.keys() 遍历自身、可枚举的属性键值
  • Object.getOwnPropertyNames() 遍历自身、可枚举、不可枚举的属性名

Symbol 属性名不会出现在 for..in,for...of 循环中,也不会被 Object.keys() Object.getOwnPropertyNames() JSON.stringify() 返回。

Object.getOwnPropertySymbols() 可以获取指定对象的所有 Symobl 属性名。

const obj = {};
const sA = Symbol('a');
const sB = Symbol('b');
obj[sA] = 'Hello';
obj[sB] = 'World';
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a), Symbol(b)]

6. Symbol.for() Symbol.keyFor()

Symbol.for() 检索有没有以传入参数作为名称的 Symbol 值,有就返回,没有就新建一个,并返回。

const s1 = Symbol.for('s');
console.log(s1); // Symbol(s)
const s2 = Symbol.for('s');
console.log(s2); // Symbol(s)
const s3 = Symbol('s');
console.log(s3); // Symbol(s)
console.log(s1 === s2); // true
console.log(s1 === s3); // false
const s1 = Symbol.for('s');
const s2 = Symbol('s');
console.log(Symbol.keyFor(s1)); // s
console.log(Symbol.keyFor(s2)); // undefined

7. 实例:模块的 Singleton 模式

略...

8. 内置的 Symbol 值

  • Symbol.hasInstance

对象的 Symbol.hasInstance 属性指向内部的一个方法,调用 foo instanceof Foo 相当于调用了 Foo[Symbol.hasInstance](foo)

class MyClass {
  [Symbol.hasInstance](foo) {
    console.log(foo instanceof Array);
    return foo instanceof Array;
  }
}
[] instanceof new MyClass(); // true <-- 会自动调用 MyClass 的 Symbol.hasInstance 属性所指向的方法
  • Symbol.isConcatSpreadable

这个属性是一个布尔值,表示该对象用于 Array.prototype.concat() 时,是否可展开。

const arr1 = [1, 2];
console.log(arr1[Symbol.isConcatSpreadable]); // undefined <-- 数组中 undefined 等同于 true 的效果
console.log([3, 4].concat(arr1)); // [3, 4, 1, 2]

const arr2 = [1, 2];
arr2[Symbol.isConcatSpreadable] = false; // <-- 设置为 false
console.log([3, 4].concat(arr2)); // [3, 4, [1, 2]] <-- 不可展开

类似数组的对象正好相反。

const obj1 = {
  0: 'a',
  1: 'b',
  length: 2,
};
console.log(obj1[Symbol.isConcatSpreadable]); // undefined <-- 累数组的对象中 undefined 等同于 false 的效果
console.log([1, 2].concat(obj1)); // 【1, 2, {0: 'a', 1: 'b', length: 2}] <-- 不可展开

const obj2 = {
  0: 'a',
  1: 'b',
  length: 2,
};
obj2[Symbol.isConcatSpreadable] = true; // <-- 设置为 true
console.log([1, 2].concat(obj2)); // [1, 2, "a", "b"] <-- 可展开
  • Symbol.species

对象的 Symbol.species 指向一个构造函数。创建衍生对象时,会使用该属性。

class MyArray extends Array {}
const a = new MyArray(1, 2, 3);
console.log(a instanceof Array); // true
console.log(a instanceof MyArray); // true
const b = a.map(v => v);
const c = a.filter(v => v > 1);
console.log(b instanceof Array); // true
console.log(b instanceof MyArray); // true <-- b c 是 a 的衍生对象,应该是 Array 的实例,但实际上,b c 也是 MyArray 的实例
console.log(c instanceof Array); // true
console.log(c instanceof MyArray); // true <-- b c 是 a 的衍生对象,应该是 Array 的实例,但实际上,b c 也是 MyArray 的实例

Symbol.species 就是为了解决这个问题。

class MyArray extends Array {
  static get [Symbol.species]() { // 设置 Symbol.species 属性,创建衍生对象时,会调用指定的函数作为构造函数
    return Array;
  }
}
const a = new MyArray(1, 2, 3);
console.log(a instanceof Array); // true
console.log(a instanceof MyArray); // true
const b = a.map(v => v);
const c = a.filter(v => v > 1);
console.log(b instanceof Array); // true
console.log(b instanceof MyArray); // false <-- b c 不再是 MyArray 的实例
console.log(c instanceof Array); // true
console.log(c instanceof MyArray); // false <-- b c 不再是 MyArray 的实例

用途:某些类库是在基类的基础上修改的,子类使用继承方法时,作者可能希望返回基类的实例,而不是子类的实例。

  • Symbol.match

对象的 Symbol.match 指向一个函数,执行 str.macth(myObject) 时会调用它,返回该方法的返回值。

String.prototype.match(regexp);
// 等同于
regexp[Symbol.match](this);
class MyMatcher {
  [Symbol.match](str) {
    return 'Hello World'.indexOf(str);
  }
}
const matcher = new MyMatcher();
console.log('e'.match(matcher)); // 1
console.log(matcher[Symbol.match]('e')); // 1
  • Symbol.replace
Last Updated: 8/15/2019, 8:18:34 PM