zoukankan      html  css  js  c++  java
  • 深入理解 ES6 新增的数据结构 Map 与 WeakMap

    一、Map

    1、Map 的基本用法

    JavaScript 的对象(Object),本质上是键值对的集合(Hash结构),但传统上只能用字符串当作键,这给它带来很大的使用限制

    var data = {};
    var element = document.getElementById('myDiv');
    
    data[element] = 'metadata';
    data['[object HTMLDivElement]'] // "metadata"
    

    上面代码原意是将一个 DOM 节点作为对象 data 的键,但是由于对象只接受字符串作为键名,所以 element 被自动转为字符串 [object HTMLDivElement]

    为了解决这个问题,ES6 提供了 Map 数据结构
    Map 类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键

    var m = new Map();
    var o = { p: 'Hello' };
    
    m.set(o, 'World')
    m.get(o) // "World"
    

    上面代码使用 set 方法,将对象 o 当作 m 的一个键,然后又使用 get 方法读取这个键

    2、数组成员作键值对

    作为构造函数,Map 也可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组,例如:

    var map = new Map([
      ['name', '张三'],
      ['age', 22, 30]
    ]);
    
    map.size // 2
    map.get('name') // "张三"
    map.get('age') // 22
    

    上面代码在新建 Map 实例时,就指定了两个键 nametitle

    3、可覆盖前一个键的值

    如果对同一个键多次赋值,后面的值将覆盖前面的值,例如:

    let map = new Map();
    
    map
      .set(1, 'aaa')
      .set(1, 'bbb');
    
    map.get(1) // "bbb"
    

    上面代码对键 1 连续赋值两次,后一次的值覆盖前一次的值

    4、键值可重复

    同样的值的两个实例,在 Map 结构中被视为两个键,例如:

    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
    

    上面代码中,变量 k1k2 的值是一样的,但是它们在 Map 结构中被视为两个键
    由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键

    如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键
    因此,0-0 是一个键,字符串 'true' 和布尔值 true 是两个不同的键
    另外,虽然 NaN 不严格相等于自身,但 Map 将其视为同一个键

    let map = new Map();
    
    map.set(NaN, 123);
    map.get(NaN) // 123
    
    map.set(-0, 123);
    map.get(+0) // 123
    

    5、Map 实例的属性

    ① size 属性

    size 属性返回 Map 结构的成员总数

    let map = new Map();
    map.set('foo', true);
    map.set('bar', false);
    
    map.size // 2
    

    6、Map 实例的操作方法

    ① set(key, value)

    set 方法设置 key 所对应的键值,然后返回整个 Map 结构
    如果 key 已经有值,则键值会被更新,否则就新生成该键

    let m1 = new Map();
    let m2 = m1.set('name', 'Winnie')
    
    m1              // Map(1) {"name" => "Winnie"}
    m2              // Map(1) {"name" => "Winnie"}
    

    set 方法返回的是 Map 本身,因此可以采用链式写法

    let map = new Map()
      .set(1, 'a')
      .set(2, 'b')
      .set(3, 'c');
    
    map // Map(3) {1 => "a", 2 => "b", 3 => "c"}
    

    ② get(key)

    get 方法读取 key 对应的键值,如果找不到 key,返回 undefined

    let map = new Map();
    map.set('name', 'Winnie')
    
    map.name         // undefined
    map.get('name')  // "Winnie"
    map.get('age')   // undefined
    

    ③ has(key)

    has 方法返回一个布尔值,表示某个键是否在 Map 数据结构中

    let map = new Map()
      .set(1, 'a')
      .set(2, 'b')
      .set(3, 'c');
    
    map         // Map(3) {1 => "a", 2 => "b", 3 => "c"}
    map.has(1)  // true
    map.has(4)  // false
    

    ④ delete(key)

    delete方法删除某个键,返回true。如果删除失败,返回false。

    let map = new Map()
      .set(1, 'a')
      .set(2, 'b')
      .set(3, 'c');
    
    map             // Map(3) {1 => "a", 2 => "b", 3 => "c"}
    map.delete(1)   // true
    map             // Map(2) {2 => "b", 3 => "c"}
    

    ⑤ clear()

    clear方法清除所有成员,没有返回值。

    let map = new Map()
      .set(1, 'a')
      .set(2, 'b')
      .set(3, 'c');
    
    map             // Map(3) {1 => "a", 2 => "b", 3 => "c"}
    map.clear()     // undefined
    map             // Map(0) {}
    

    7、Map 实例的 遍历方法

    ① keys()

    返回键名的遍历器

    let map = new Map([
      ['name', 'Winnie'],
      ['age', 22],
    ]);
    
    for (let key of map.keys()) {
      console.log(key)
    }
    // "name"
    // "age"
    

    ② values()

    返回键值的遍历器

    let map = new Map([
      ['name', 'Winnie'],
      ['age', 22],
    ]);
    
    for (let value of map.values()) {
      console.log(value)
    }
    // "Winnie"
    // 22
    

    ③ entries()

    返回所有成员的遍历器

    let map = new Map([
      ['name', 'Winnie'],
      ['age', 22],
    ]);
    
    for (let entry of map.entries()) {
      console.log(entry)
    }
    // ["name", "Winnie"]
    // ["age", 22]
    
    for (let [key, value] of map.entries()) {
      console.log(key, value);
    }
    // "name" "Winnie"
    // "age" 22
    

    ④ forEach()

    遍历 Map 的所有成员,与数组的 forEach 方法类似,也可以实现遍历,其中第一个参数键名,第二个参数键值,第三个参数指它本身

    let map = new Map([
      ['name', 'Winnie'],
      ['age', 22],
    ]);
    
    map.forEach((value, key) => {
      console.log(key, value);
    });
    // "name" "Winnie"
    // "age" 22
    

    forEach 方法还可以接受第二个参数,用来绑定 this,例如:

    let map = new Map([
      ['name', 'Winnie'],
      ['age', 22],
    ]);
    
    let write = {
      pencil: (key, value) => {
        console.log(key, value);
      }
    };
    
    map.forEach(function (value, key) {
      this.pencil(key, value);
    }, write);
    // "name" "Winnie"
    // "age" 22
    

    上面代码中,forEach 方法的回调函数的 this,就指向 write

    8、Map 数据结构转换

    ① Map 转数组

    Map 结构转数组结构,可以使用扩展运算符(...)

    let map = new Map([
      [1, 'one'],
      [2, 'two'],
      [3, 'three'],
    ]);
    
    [...map.keys()]
    // [1, 2, 3]
    
    [...map.values()]
    // ['one', 'two', 'three']
    
    [...map.entries()]
    // [[1,'one'], [2, 'two'], [3, 'three']]
    
    [...map]
    // [[1,'one'], [2, 'two'], [3, 'three']]
    

    ② 数组转 Map

    将数组转入 Map 构造函数,就可以转为 Map

    let map = new Map([
      [1, 'one'],
      [2, 'two'],
      [3, 'three'],
    ]);
    
    map // Map(3) {1 => "one", 2 => "two", 3 => "three"}
    

    ③ Map 转对象

    如果所有 Map 的键都是字符串,它可以转为对象

    let map = new Map([
      ['name', 'Winnie'],
      ['age', 22],
    ]);
    
    function strMapToObj(strMap) {
      let obj = Object.create(null);
      for (let [key, val] of strMap) {
        obj[key] = val;
      }
      return obj;
    }
    
    let obj = strMapToObj(map)
    
    console.log(obj)  // {name: "Winnie", age: 22}
    

    ④ 对象转 Map

    let obj = {
      name: "Winnie",
      age: 22
    };
    
    function objToStrMap(obj) {
      let strMap = new Map();
      for (let key of Object.keys(obj)) {
        strMap.set(key, obj[key]);
      }
      return strMap;
    }
    
    let map = objToStrMap(obj)
    
    console.log(map)  // Map(2) {"name" => "Winnie", "age" => 22}
    

    ⑤ Map 转 JSON

    Map 的键名都是字符串时,可以选择转为对象 JSON

    let map = new Map([
      ['name', 'Winnie'],
      ['age', 22],
    ]);
    
    function strMapToObj(strMap) {
      let obj = Object.create(null);
      for (let [key, val] of strMap) {
        obj[key] = val;
      }
      return obj;
    }
    
    function strMapToJson(strMap) {
      return JSON.stringify(strMapToObj(strMap));
    }
    
    let json = strMapToJson(map)
    
    console.log(json)  // '{ "name": "Winnie", "age": 22 }'
    

    Map 的键名有非字符串时,可以选择转为数组 JSON

    let map = new Map([
      [1, 'one'],
      [2, 'two'],
      [true, 'okk']
    ]);
    
    function mapToArrayJson(map) {
      return JSON.stringify([...map]);
    }
    
    let json = mapToArrayJson(map)
    
    console.log(json)  // '[[1, "one"], [2, "two"], [true, "okk"]]'
    

    ⑥ JSON 转 Map

    JSON 转为 Map,正常情况下所有键名都是字符串

    let json = '{ "name": "Winnie", "age": 22 }';
    
    function objToStrMap(obj) {
      let strMap = new Map();
      for (let key of Object.keys(obj)) {
        strMap.set(key, obj[key]);
      }
      return strMap;
    }
    
    function jsonToStrMap(jsonStr) {
      return objToStrMap(JSON.parse(jsonStr));
    }
    
    let map = jsonToStrMap(json)
    
    console.log(map)  // Map(2) {"name" => "Winnie", "age" => 22}
    
    

    当整个 JSON 是一个数组,且每个数组本身又是有两个成员的数组时,可以一一对应地转为 Map,这往往是数组转为 JSON 的逆操作

    let json = '[[1, "one"], [2, "two"], [true, "okk"]]';
    
    function jsonToMap(jsonStr) {
      return new Map(JSON.parse(jsonStr));
    }
    
    let map = jsonToMap(json)
    
    console.log(map)  // Map(3) {1 => "one", 2 => "two", true => "okk"}
    

    二、WeakMap

    1、WeakMap 的基本用法

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

    下面是 WeakMap 结构的一个例子:

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

    上面代码中,变量 wm 是一个 WeakMap 实例
    我们将一个 DOM 节点 div1 作为键名,然后销毁这个节点,div1 对应的键就自动消失了,再引用这个键名就返回 undefined

    2、WeakMap 与 Map 的区别

    ① WeakMap 只接受对象作为键名(null除外),而且键名所指向的对象,不计入垃圾回收机制

    var 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
    

    上面代码中,如果将 1Symbol 作为 WeakMap 的键名,都会报错,因为不是对象

    ② WeakMap 没有 size 属性

    ③ WeakMap 没有遍历操作

    WeakMap 没有 key()values()entries() 方法,而且无法清空(即不支持 clear 方法),这与 WeakMap 的键不被计入引用、被垃圾回收机制忽略有关

    3、WeakMap 实例的操作方法

    除了没有 clear() 方法,其它跟 Map 一样,都含有 get()set()has()delete()

  • 相关阅读:
    win10 uwp 商业游戏 1.1.5
    PHP ftp_exec() 函数
    PHP ftp_delete() 函数
    PHP ftp_connect() 函数
    PHP ftp_close() 函数
    PHP ftp_chmod() 函数
    grant 之后是否要跟着 flush privileges
    [TJOI2015]概率论
    win10 uwp 商业游戏 1.1.5
    Total Commander 显示文件包含文件名扩展
  • 原文地址:https://www.cnblogs.com/Leophen/p/14794561.html
Copyright © 2011-2022 走看看