ES6之Symbol(符号)

177 阅读5分钟

普通符号

什么是符号?

符号是ES6新增的一个数据类型,属于原始值

原始值:developer.mozilla.org/zh-CN/docs/…

js数据类型与数据结构:developer.mozilla.org/zh-CN/docs/…

怎么创建符号?

我们之前学习的number,string,boolean等,都是可以通过字面量的形式创建,但是符号只能通过执行函数Symbol(描述信息)创建,没有其他的创建方式。

const symb = Symbol('这是一个符号');
console.log(symb); // Symbol(这是一个符号)
console.log(typeof symb); // symbol

符号有什么用?

添加符号类型的初衷,是为了给对象设置私有属性。

我们看下面这个例子:

const player = {
  aggressivity: 30, // 攻击力
  defence: 10, // 防御力
  hp: 100, // 血量
  attack() { // 攻击
    // 执行攻击函数,随机产生伤害,伤害 = 攻击力 * 随机百分比
    const hurt = this.aggressivity * this.getRandom(0.6, 1);
    console.log(hurt);
  },
  getRandom(min: number = 0, max: number = 1) {
    return Math.random() * (max - min) + min;
  }
}

在这个例子中,玩家对象具有攻击力,防御力,血量三个属性,和一个攻击方法,执行攻击方法可以产生一个随机的伤害值,伤害值在攻击力的60%~100%之间随机获取,因此对象中有一个获取随机数的函数。

现在,外部可以访问到player对象的所有属性,但是我们会发现,player.getRandom这个方法,对于外部来说是没必要暴露出去的,暴露出去后不仅没什么用,反而增加了这个对象的使用难度。

解决这个问题,通常我们有以下两种方法:

  1. 将getRandom函数定义在attack函数内部,但是这样会有以下问题:

    1. 每次执行attack函数的时候,都会重新创建一个getRandom函数
    2. 如果player对象的其他地方也要使用getRandom函数,是没有办法共享的
  2. 将getRandom函数定义在外部,这样player对象就不会有getRandom这个方法,但是这样也会有问题:

    1. 如果这个getRandom函数需要用到player对象的其他属性呢?这个方法又行不通了。

造成以上问题的根本原因,就是缺失“私有属性”

符号的特点

  1. 每次调用Symbol函数创建的符号,永远不相等,不管符号描述是否一样。这也是为什么符号可以作为私有属性的原因。
const syb1 = Symbol('desc');
const syb2 = Symbol('desc');
console.log(syb1 === syb2) // false
  1. 符号可以作为对象的属性名存在,我们称之为“符号属性”

在符号出现之前,我们对象的属性名全部是以string的形式存在

  1. 由于符号“唯一”的特点,开发者可以通过精心设计,让符号属性无法被外界访问
const player = (() => {
  const getRandomSyb = Symbol('get-random');
  return {
    aggressivity: 30, // 攻击力
    defence: 10, // 防御力
    hp: 100, // 血量
    attack() { // 攻击
      // 执行攻击函数,随机产生伤害,伤害 = 攻击力 * 随机百分比
      const hurt = this.aggressivity * this[getRandomSyb](0.6, 1);
      console.log(hurt);
    },
    [getRandomSyb](min: number = 0, max: number = 1) {
      return Math.random() * (max - min) + min;
    }
  }
})();
  1. 符号属性不可枚举,所以是无法在forin循环中获取到符号属性,Object.keys方法也无法获取,即使是可以获取到所有无法枚举属性的Object.getOwnPropertyNames也无能为力
  2. 但是,ES6为了防止某些特殊情况下必须获取符号属性,ES6给我们提供了一个Object.getOwnPropertySymbols方法让我们可以获取到符号属性
  3. 符号无法进行隐式类型转换,像数学运算、字符串拼接等等,都不可以。但是符号可以通过String()显示类型转换为字符串,console.log输出的符号就是先通过String将符号转化为字符串,然后再将字符串染色输出到控制台。

共享符号

通过“普通符号”知道,我们可以通过Object.getOwnPropertySymbols获取符号属性,那我们有没有一种更加简单方式获取同一个符号呢?有,这就是共享符号。

什么是共享符号?

可以根据符号描述来获取同一个符号的符号,换句话说,只要符号的描述信息相同,符号就相同

怎么创建共享符号?

通过Symbol.for(描述信息) 创建

const syb1 = Symbol.for('desc');
const syb2 = Symbol.for('desc');
console.log(syb1 === syb2) // true

我们运用共享符号改写player对象

const player = (() => {
  const getRandomSyb = Symbol.for('get-random');
  return {
    aggressivity: 30, // 攻击力
    defence: 10, // 防御力
    hp: 100, // 血量
    attack() { // 攻击
      // 执行攻击函数,随机产生伤害,伤害 = 攻击力 * 随机百分比
      const hurt = this.aggressivity * this[getRandomSyb](0.6, 1);
      console.log(hurt);
    },
    [getRandomSyb](min: number = 0, max: number = 1) {
      return Math.random() * (max - min) + min;
    }
  }
})();

console.log(player[Symbol.for('get-random')]); // 可以访问到

此时我们可以通过创建含有相同“描述信息”的符号,来获取对应的符号属性。

共享符号平常使用很少,因为既然我们允许外面访问属性,那我们直接使用字符串做属性名就可以啦!

实现一个共享符号

const SymbolFor = (() => {
  const global = {};
  return (name: string) => {
    if (!(name in global)) {
      global[name] = Symbol(name);
    }
    return global[name];
  }
})();

知名符号

什么是知名符号?

知名符号是一些具有特殊含义的“共享符号”,知名符号不可以创建,但是JS内部本身就创建好了一些知名符号,我们可以Symbol的静态属性得到知名符号。

知名符号有什么用?

减少魔法,暴露内部实现。

知名符号举例

  1. Symbol.hasInstance

这个符号用于判断一个对象是否是某个构造函数的实例,使用它可以修改instanceof的判定行为。

u instanceof User等效于User[Symbol.hasInstance](u),很明显,现在Symbol.hasInstance这个符号是构造函数User上的一个静态方法,我们可以通过给这个符号重新赋值来自定义它的行为

class User {
  static [Symbol.hasInstance](ins) {
    return false;
  }
}
const u = new User();
console.log(u instanceof User);

这样 instanceof 在User构造函数上就永远返回false了。

现在,我们就可以参与instaceof的内部实现啦!

  1. 其他知名符号:

developer.mozilla.org/zh-CN/docs/…