Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Map

1. 完整的例子

let map = new Map();
map.set("foo", 1);
map.set("bar", 2);

//或者一步到位直接初始化
let map = new Map([
  ["foo", 1],
  ["bar", 2],
]);

let value1 = map.get("foo");
console.log(value1); // 1

let value2 = map.has("bar");
console.log(value2); // true

map.delete("bar");

let value3 = map.get("bar");
console.log(value3); // undefined

map.clear();

let value4 = map.size;
console.log(value4); // 0

其实 javescript 对于 Object 的原生支持,就已经起到了 Map 的作用

比如上面的可以这样表达

let map = {};
map["foo"] = 1;
map["bar"] = 2;

//或者一步到位直接初始化
let map = {
  foo: 1,
  bar: 2,
};

let value1 = map["foo"];
console.log(value1); // 1

let value2 = map["bar"] !== undefined;
console.log(value2); // true

delete map["bar"];

let value3 = map["bar"];
console.log(value3); // undefined

map = {};

let value4 = Object.keys(map).length;
console.log(value4); // 0
  • 用 Object 模拟时,要注意

    • 判断对象是否存在某个属性键时,更准确地要用 ES2022 中的hasOwn

      let map = {
        foo: 1,
        bar: 2,
      };
      
      let value2 = Object.hasOwn(map, "bar");
      console.log(value2); // true
      
    • 判断对象的长度时,用 keys 返回的是可枚举的属性,如果要包括enumerable: false的话,需要用getOwnPropertyNames

      let map = {
        foo: 1,
        bar: 2,
      };
      
      let value4 = Object.getOwnPropertyNames(map).length;
      console.log(value4); // 2
      
      • 以下是二者区别的测试代码

        const obj = {};
        Object.defineProperties(obj, {
          property1: { enumerable: true, value: 1 },
          property2: { enumerable: false, value: 2 },
        });
        
        console.log(Object.keys(obj)); // ["property1"]
        console.log(Object.getOwnPropertyNames(obj)); // ["property1", "property2"]
        

2. 要点

  • Map 可以使用任何类型作为键,而对象只能使用字符串或 Symbol

    let map = new Map();
    let objKey = { id: 1 };
    let funcKey = function () {};
    
    map.set(objKey, "对象作为键");
    map.set(funcKey, "函数作为键");
    map.set(123, "数字作为键");
    
    console.log(map.get(objKey)); // "对象作为键"
    console.log(map.get(funcKey)); // "函数作为键"
    console.log(map.get(123)); // "数字作为键"
    
    // 对象只能用字符串或 Symbol
    let obj = {};
    obj[objKey] = "值"; // objKey 会被转换为字符串 "[object Object]"
    console.log(obj["[object Object]"]); // "值"
    
  • Map 保持插入顺序,迭代时按照插入顺序进行

    let map = new Map();
    map.set("z", 1);
    map.set("a", 2);
    map.set("m", 3);
    
    for (let [key, value] of map) {
      console.log(key); // 输出顺序:z, a, m(按插入顺序)
    }
    
  • Map 提供了丰富的迭代方法

    let map = new Map([
      ["foo", 1],
      ["bar", 2],
    ]);
    
    // keys() - 获取所有键
    for (let key of map.keys()) {
      console.log(key); // "foo", "bar"
    }
    
    // values() - 获取所有值
    for (let value of map.values()) {
      console.log(value); // 1, 2
    }
    
    // entries() - 获取所有键值对
    for (let [key, value] of map.entries()) {
      console.log(key, value); // "foo" 1, "bar" 2
    }
    
    // forEach
    map.forEach((value, key) => {
      console.log(key, value);
    });
    
  • Map 没有原型链污染问题,更加安全

    // 对象可能会有原型链上的属性
    let obj = {};
    console.log(obj["toString"]); // function toString() { [native code] }
    console.log("toString" in obj); // true(来自原型链)
    
    // Map 不会有这个问题
    let map = new Map();
    console.log(map.get("toString")); // undefined
    console.log(map.has("toString")); // false
    
  • 在频繁增删键值对的场景下,Map 的性能优于对象

    // Map 在大量增删操作时性能更好
    let map = new Map();
    for (let i = 0; i < 1000000; i++) {
      map.set(`key${i}`, i);
    }
    for (let i = 0; i < 1000000; i++) {
      map.delete(`key${i}`);
    }
    
  • Map 与对象的转换

    // 对象转 Map
    let obj = { foo: 1, bar: 2 };
    let map = new Map(Object.entries(obj));
    
    // Map 转对象
    let map = new Map([
      ["foo", 1],
      ["bar", 2],
    ]);
    let obj = Object.fromEntries(map);
    console.log(obj); // { foo: 1, bar: 2 }
    
  • WeakMap:弱引用版本的 Map

    // WeakMap 的键必须是对象,且是弱引用
    let wm = new WeakMap();
    let key = { id: 1 };
    
    wm.set(key, "值");
    console.log(wm.get(key)); // "值"
    
    // 当 key 没有其他引用时,会被垃圾回收
    key = null; // WeakMap 中的条目也会被自动清理
    
    // WeakMap 没有 size、clear、keys、values、entries 等方法
    // 只有 get、set、has、delete
    
    • WeakMap 的典型应用场景:存储对象的私有数据、缓存

      // 用 WeakMap 存储 DOM 元素的关联数据
      let elementData = new WeakMap();
      
      let element = document.querySelector("#myElement");
      elementData.set(element, { clicks: 0 });
      
      // 当 element 从 DOM 移除且没有其他引用时
      // WeakMap 中的数据会被自动清理,避免内存泄漏