ES6经常使用但被忽略的方法(第三弹)

Symbol

特性

  1. 惟一性
  • 属性名属于 Symbol 类型的,都是独一无二的,能够保证不会与其余属性名产生冲突。即便是两个声明彻底同样的也是不相等的。
// 没有参数的状况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false

// 有参数的状况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
复制代码
  1. 不能和其余类型运算
  • Symbol 值不能与其余类型的值进行运算,会报错。
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string `your symbol is ${sym}` // TypeError: can't convert symbol to string
复制代码
  1. 类型转换
  • Symbol 值能够显式转为字符串和布尔值,可是不能转为数值。
let sym = Symbol('My symbol');

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'

let sym = Symbol();
Boolean(sym) // true
!sym  // false

Number(sym) // TypeError
sym + 2 // TypeError
复制代码

Symbol的应用

  1. 常见的惟一值
  • Symbol自己的特性就是任何两个Symbol类型的值都不相等,因此咱们能够在不知到命名变量时,都设置为Symbol类型。Symbol类型不能使用new操做符,由于生成的 Symbol 是一个原始类型的值,不是对象。
  1. 私有属性
  • 因为Symbol类型的做为属性名时,遍历对象的时候,该属性不会出如今for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。须要经过Object.getOwnPropertySymbols()方法,能够获取指定对象的全部 Symbol 属性名。这样就能够把Symbol类型的属性做为私有属性。
  • 新的 APIReflect.ownKeys()方法能够返回全部类型的键名,包括常规键名和 Symbol 键名。
  1. 消除魔法字符串
  • 魔术字符串指的是,在代码之中屡次出现、与代码造成强耦合的某一个具体的字符串或者数值。咱们能够把对应的字符串或数值设置成Symbol类型便可。
const name = {
    first: Symbol('detanx')
}
function getName(firstName) {
  switch (firstName) {
    case name.first: // 魔术字符串
      ...
  }
}

getName(name.first); // 魔术字符串
复制代码

Symbol.for()和Symbol.keyFor()

  • Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,若是不存在才会新建一个值。即若是传入相同的key建立的值是相等的。而Symbol()每次建立的值都不相同,即便key相同。
let s1 = Symbol.for('detanx');
let s2 = Symbol.for('detanx');
s1 === s2 // true

let s1 = Symbol('detanx');
let s2 = Symbol('detanx');
s1 === s2 // false
复制代码
  • Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。未登记的 Symbol 值,返回undefined
let s1 = Symbol.for("detanx");
Symbol.keyFor(s1) // "detanx"

let s2 = Symbol("detanx");
Symbol.keyFor(s2) // undefined
复制代码
  • Symbol()写法没有登记机制,因此每次调用都会返回一个不一样的值。
  • Symbol.for()Symbol 值登记的名字,是全局环境的,无论有没有在全局环境运行。

内置的Symbol值

Set 和 Map 数据结构

Set

  1. 特性
    • 相似于数组,可是成员的值都是惟一的,没有重复的值。
    • Set函数能够接受一个数组(或者具备 iterable 接口的其余数据结构)做为参数,用来初始化。
    • 内部两个数据是否相同基本和===相似,区别是在SetNaNNaN也是相等的。
  2. 应用
    • 数组或字符串去重。
    [...new Set([1, 1, 2, 3])] // [1, 2, 3]
    
    [...new Set('ababbc')].join('') // "abc"
    复制代码
    • 某些须要惟一性的数据,例如:用户名,id等。
  3. Set 实例的属性和方法
    • Set 结构的实例有如下属性。
    Set.prototype.constructor:构造函数,默认就是Set函数。
    Set.prototype.size:返回Set实例的成员总数。
    复制代码
    • 操做方法(用于操做数据
    Set.prototype.add(value):添加某个值,返回 Set 结构自己。
    Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
    Set.prototype.clear():清除全部成员,没有返回值。
    复制代码
    • 遍历方法(用于遍历成员)
    Set.prototype.keys():返回键名的遍历器
    Set.prototype.values():返回键值的遍历器
    Set.prototype.entries():返回键值对的遍历器
    Set.prototype.forEach():使用回调函数遍历每一个成员
    复制代码

WeakSet

  1. Set的区别
  • WeakSet 的成员只能是对象(null除外),而不能是其余类型的值。
const ws = new WeakSet();
ws.add(1) // TypeError: Invalid value used in weak set
ws.add(Symbol())  // TypeError: invalid value used in weak set
ws.add(null)  // TypeError: invalid value used in weak set
复制代码
  • WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑WeakSet 对该对象的引用,也就是说,若是其余对象都再也不引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet之中。
  1. 方法。
WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
复制代码
  • WeakSet 没有size属性,没有办法遍历它的成员。

Map

  • 因为传统的Object对象只能使用字符串看成键,因此新增的Map结构能够将各类类型的值(包括对象)均可以看成键。
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"
复制代码
  1. 方法
  • 相比Set的操做方法,Map没有add方法,新增了get方法和set方法。遍历方法则是基本是同样的。
Map.prototype.get(key) // 读取key对应的键值,若是找不到key,返回undefined。
Map.prototype.has(key) // 返回一个布尔值,表示某个键是否在当前 Map 对象之中。
复制代码
  1. 转换
    (1) Map和数组
    • 在第二弹中也提到了Map和数组之间转换,他们之间互转是很方便的。
    // Map转为数组
    const myMap = new Map()
      .set(true, 7)
      .set({foo: 3}, ['abc']);
    [...myMap]
    // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
    
    // 数组转为 Map。
    new Map([
      [true, 7],
      [{foo: 3}, ['abc']]
    ])
    // Map {
    //   true => 7,
    //   Object {foo: 3} => ['abc']
    // }
    复制代码
    (2) Map和对象
    • Map 的键都是字符串,它能够无损地转为对象。若是有非字符串的键名,那么这个键名会被转成字符串,再做为对象的键名。对象转为 Map 能够经过Object.entries()
    // Map => Object
    function strMapToObj(strMap) {
      let obj = Object.create(null);
      for (let [k,v] of strMap) {
        obj[k] = v;
      }
      return obj;
    }
    
    const myMap = new Map()
      .set('yes', true)
      .set('no', false);
    strMapToObj(myMap)
    // { yes: true, no: false }
    
    // Object => Map
    // let obj = {"a":1, "b":2};
    let map = new Map(Object.entries(obj));
    复制代码
  2. 应用
  • 在存储的数据是键值对的时候,而且键名的类型多是多种类型是可使用Map结构。java中的Map结构有区别。

WeakMap

  1. Map的区别
    • WeakMap只接受对象做为键名(null除外),不接受其余类型的值做为键名。
    const map = new WeakMap();
    map.set(1, 2) // TypeError: 1 is not an object!
    map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key
    map.set(null, 2) // TypeError: Invalid value used as weak map key
    map.set(new Number(1), 2) // WeakMap {Number => 2}
    复制代码
    • WeakMap的键名所指向的对象,不计入垃圾回收机制。一旦再也不须要这两个对象,咱们就必须手动删除这个引用,不然垃圾回收机制就不会释放占用的内存。
    const e1 = document.getElementById('foo');
    const e2 = document.getElementById('bar');
    const arr = [
      [e1, 'foo 元素'],
      [e2, 'bar 元素'],
    ];
    // 不须要 e1 和 e2 的时候
    // 必须手动删除引用
    arr [0] = null;
    arr [1] = null;
    复制代码
    • WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
    • 没有遍历操做(即没有keys()values()entries()方法),也没有size属性。
    • 没法清空,即不支持clear方法。WeakMap只有四个方法可用:get()set()has()delete()
  2. 应用
    (1) DOM 节点做为键名
    • document.getElementById('logo')是一个DOM 节点,每当发生click事件,就更新一下状态。咱们将这个状态做为键值放在 WeakMap 里,对应的键名就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。
    let myWeakmap = new WeakMap();
    
    myWeakmap.set(
      document.getElementById('logo'),
      {timesClicked: 0})
    ;
    
    document.getElementById('logo').addEventListener('click', function() {
      let logoData = myWeakmap.get(document.getElementById('logo'));
      logoData.timesClicked++;
    }, false);
    复制代码
    (2) 部署私有属性
    • 内部属性是实例的弱引用,因此若是删除实例,它们也就随之消失,不会形成内存泄漏。
    const _counter = new WeakMap();
    const _action = new WeakMap();
    
    class Countdown {
      constructor(counter, action) {
        _counter.set(this, counter);
        _action.set(this, action);
      }
      dec() {
        let counter = _counter.get(this);
        if (counter < 1) return;
        counter--;
        _counter.set(this, counter);
        if (counter === 0) {
          _action.get(this)();
        }
      }
    }
    
    const c = new Countdown(2, () => console.log('DONE'));
    
    c.dec()
    c.dec()
    // DONE
    复制代码