美文网首页
阮一峰es6要点总结——Set与Map数据结构

阮一峰es6要点总结——Set与Map数据结构

作者: 布蕾布蕾 | 来源:发表于2017-04-03 18:40 被阅读0次

    笔记,完全摘录自阮一峰

    set数据结构

    ES6提供的,新的数据结构,类似于数组。

    特点:成员值唯一,没有重复的值

    • 生成set数据结构
      使用Set()构造函数,可接受一个数组,类数组对象作为参数。

    • Set实例的方法和属性
      属性

    • Set.prototype.constructor:指向构造函数Set

    • Set.prototype.size:返回set实例的成员总数

    方法——操作方法(操作数据)/遍历方法(遍历成员)

    • 操作方法
      • add(value):添加value,并返回Set结构本身
      • delete(value):删除value,返回布尔值,表示是否删除成功
      • has(value):返回布尔值,表示value是否为set的成员
      • clear():清除所有成员
    • 遍历操作
    • key():返回键名的遍历器
    • values():返回键值的遍历器
    • entries():返回键值对的遍历器
    • forEach():使用回调函数遍历每个成员

    Set的遍历顺序就是插入顺序

    由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

    Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。

    • Set内部的值相等判断
      使用“Same-value equality”算法,类似全等===,区别是,NaN等于自身。

    • 应用

    1.利用Set结构的独特特性,实现数组去重

    // 去除数组的重复成员
    [...new Set(array)]
    

    2.实现并集、交集、差集

    let a = new Set([1, 2, 3]);
    let b = new Set([4, 3, 2]);
    // 并集
    let union = new Set([...a, ...b]);
    // Set {1, 2, 3, 4}
    // 交集
    let intersect = new Set([...a].filter(x => b.has(x)));
    // set {2, 3}
    // 差集
    let difference = new Set([...a].filter(x => !b.has(x)));
    // Set {1}
    

    3.在遍历中,改变原Set结构

    // 方法一,利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构
    let set = new Set([1, 2, 3]);
    set = new Set([...set].map(val => val * 2));
    // set的值是2, 4, 6
    // 方法二,利用`Array.from()`
    let set = new Set([1, 2, 3]);
    set = new Set(Array.from(set, val => val * 2));
    // set的值是2, 4, 6
    

    WeakSet

    特点

    • 成员唯一,且必须是对象。对象均为弱引用

    • 弱引用性:如果没有其他对象引用WeakSet中的对象,那么垃圾回收机制会自动回收该对象占用的内存。

    • 由于弱引用性,导致WeakSet的成员不可遍历(因为无法确保遍历成员的存在)。

    用法

    构造函数WeakSet(),可传入数组,或类数组等具有iterable接口的对象。

    注意:成为WeakSet的成员的是参数的成员,而不是参数本身,所以

    var b = [3, 4];
    var ws = new WeakSet(b);
    // Uncaught TypeError: Invalid value used in weak set(…)
    

    数组b的成员是数字,不符合WeakSet的特性,会报错。其他情况的参数也一样。

    方法

    • WeakSet.prototype.add(value):添加新成员
    • WeakSet.prototype.delete(value):清除指定成员
    • WeakSet.prototype.has(value):返回布尔值,表示某个值是否存在。

    应用
    存储dom节点,不用担心,这些节点从文档移除是,造成内存泄漏

    举例:闭包保存下一个dom节点,当dom节点删除时候,由于还存在引用,无法被回收。使用WeakSet就可以解决这个问题。

    另一个例子

    const foos = new WeakSet()
    class Foo {
      constructor() {
        foos.add(this)
      }
      method () {
        if (!foos.has(this)) {
          throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
        }
      }
    

    上面代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用WeakSet的好处是,foos对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑foos,也不会出现内存泄漏。

    Map

    ObjectMap均为Hash结构(键值对集合)

    区别
    Object键名只能是字符串形式,Map的键名可以是任意类型(包括对象)。

    总的来说,Map是一种更完善的Hash结构的实现。如果你需要“键值对”的数据结构,Map比Object更合适。

    构造函数
    可传入一个数组参数。

    
    
    var items = [
      ['name', '张三'],
      ['title', 'Author']
    ];
    
    var map = new Map(items) //等价于下边的
    
    var map = new Map();
    items.forEach(([key, value]) => map.set(key, value));
    

    特性

    • Map键名可以是任意类型
    • Map键名为对象类型时,与内存地址绑定,如果内存地址不同,就视为两个键
    var map = new Map();
    
     map.set(['a'], 555);
     map.get(['a']) // undefined
     
     var map = new Map();
    
     var k1 = ['a'];
     var k2 = ['a'];
    
     map
    .set(k1, 111)
    .set(k2, 222);
    
     map.get(k1) // 111
     map.get(k2) // 222
    
    • Map键名为简单类型(数值,字符串,布尔值),只要两个值严格相等(===),就认为是一个键。
    • NaN被视为同一个键,0-0被视为同一个键
    • 相同键多次赋值,后覆盖前

    实例的属性和方法

    • size属性
      返回Map成员总数

    • set(key, value)
      设置键值对,返回Map本身,所以可以链式

    • get(key)
      读取key对应的值,找不到返回undefined

    • has(key)
      返回布尔值,表示是否存在

    • delete(key)
      删除某键,返回布尔值,表示是否成功删除

    • clear()
      清空所有成员,无返回值。

    • 遍历方法

    • keys()

    • values()

    • entries()

    • forEach(),接受第二个参数,绑定回调函数的this

    需要特别注意的是,Map的遍历顺序就是插入顺序。
    基本同Set
    Map结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。

    与其他数据结构的互相转换

    • Map-->Array
      使用扩展运算符(...)
    let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
    [...myMap]
    // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
    
    • Array-->Map
    new Map([[true, 7], [{foo: 3}, ['abc']]])
    // Map {true => 7, Object {foo: 3} => ['abc']}
    
    • Map-->Object
      键名转成字符串就行
    function strMapToObj(strMap) {
      let obj = Object.create(null);
      for (let [k,v] of strMap) {
        obj[k] = v;
      }
      return obj;
    }
    
     let myMap = new Map().set('yes', true).set('no', false);
    strMapToObj(myMap)
    // { yes: true, no: false }
    
    • Object-->Map
    function objToStrMap(obj) {
     let strMap = new Map();
     for (let k of Object.keys(obj)) {
       strMap.set(k, obj[k]);
     }
     return strMap;
    }
    objToStrMap({yes: true, no: false})
    // [ [ 'yes', true ], [ 'no', false ] ]
    
    • Map-->JSON

    两种情况
    一.键名全是字符串的,调用上边的,Map-->Object

    function strMapToJson(strMap) {
      return JSON.stringify(strMapToObj(strMap));
    }
    
    let myMap = new Map().set('yes', true).set('no', false);
    strMapToJson(myMap)
    // '{"yes":true,"no":false}'
    

    二.键名存在非字符串的,调用上边的Map-->Array

    function mapToArrayJson(map) {
      return JSON.stringify([...map]);
    }
    
    let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
    mapToArrayJson(myMap)
    // '[[true,7],[{"foo":3},["abc"]]]'
    
    • JSON-->Map

    一.正常情况

    function jsonToStrMap(jsonStr) {
      return objToStrMap(JSON.parse(jsonStr));
    }
    
    jsonToStrMap('{"yes":true,"no":false}')
    // Map {'yes' => true, 'no' => false}
    

    二.特殊情况,整个json就是一个数组,每项都是两成员数组

    unction jsonToMap(jsonStr) {
      return new Map(JSON.parse(jsonStr));
    }
    
    jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
    // Map {true => 7, Object {foo: 3} => ['abc']}
    

    WeakMap

    WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制

    WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

    一个例子

    var wm = new WeakMap();
    var element = document.querySelector(".element");
    wm.set(element, "Original");
    wm.get(element) // "Original"
    element.parentNode.removeChild(element);
    element = null;
    wm.get(element) // undefined
    

    与Map的区别

    • 没有遍历操作
    • 没有size
    • 无法清空

    这些都是因为WeakMap的键不被计入引用,被垃圾回收机制忽略。

    WeakMap的方法

    • get
    • set
    • has
    • delete

    WeakMap应用的典型例子

    例子一

    let myElement = document.getElementById('logo');
    let myWeakmap = new WeakMap();
    
    myWeakmap.set(myElement, {timesClicked: 0});
    
    myElement.addEventListener('click', function() {
      let logoData = myWeakmap.get(myElement);
      logoData.timesClicked++;
    }, false);
    

    上面代码中,myElement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是myElement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

    例子二
    部署私有属性

    let _counter = new WeakMap();
    let _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)();
        }
      }
    }
    
    let c = new Countdown(2, () => console.log('DONE'));
    
    c.dec()
    c.dec()
    // DONE
    

    上面代码中,Countdown类的两个内部属性_counter_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。

    相关文章

      网友评论

          本文标题:阮一峰es6要点总结——Set与Map数据结构

          本文链接:https://www.haomeiwen.com/subject/zsmnottx.html