• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • Map

    Map对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。

    描述

    Map对象是键值对的集合。Map中的一个键只能出现一次;它在Map的集合中是独一无二的。Map对象按键值对迭代——一个for...of循环在每次迭代后会返回一个形式为[key,value]的数组。迭代按插入顺序进行,即键值对按set()方法首次插入到集合中的顺序(也就是说,当调用set()时,map 中没有具有相同值的键)进行迭代。

    规范要求 map 实现“平均访问时间与集合中的元素数量呈次线性关系”。因此,它可以在内部表示为哈希表(使用 O(1)查找)、搜索树(使用 O(log(N))查找)或任何其他数据结构,只要复杂度小于 O(N)。


    键的相等

    键的比较基于零值相等算法。(它曾经使用同值相等,将0-0视为不同。检查浏览器兼容性。)这意味着NaN是与NaN相等的(虽然NaN !== NaN),剩下所有其它的值是根据===运算符的结果判断是否相等。


    ObjectMap的比较

    ObjectMap类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成Map使用。

    不过MapObject有一些重要的区别,在下列情况中使用Map会是更好的选择:

    MapObject
    意外的键Map默认情况不包含任何键。只包含显式插入的键。

    一个Object有一个原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

    备注:虽然可以用Object.create(null)来创建一个没有原型的对象,但是这种用法不太常见。

    键的类型一个Map的键可以是任意值,包括函数、对象或任意基本类型。一个Object的键必须是一个String或是Symbol
    键的顺序

    Map中的键是有序的。因此,当迭代的时候,一个Map对象以插入的顺序返回键值。

    虽然Object的键目前是有序的,但并不总是这样,而且这个顺序是复杂的。因此,最好不要依赖属性的顺序。

    自 ECMAScript 2015 规范以来,对象的属性被定义为是有序的;ECMAScript 2020 则额外定义了继承属性的顺序。参见 OrdinaryOwnPropertyKeys 和 EnumerateObjectProperties 抽象规范说明。但是,请注意没有可以迭代对象所有属性的机制,每一种机制只包含了属性的不同子集。(for-in仅包含了以字符串为键的属性;Object.keys仅包含了对象自身的、可枚举的、以字符串为键的属性;Object.getOwnPropertyNames包含了所有以字符串为键的属性,即使是不可枚举的;Object.getOwnPropertySymbols与前者类似,但其包含的是以Symbol为键的属性,等等。)

    SizeMap的键值对个数可以轻易地通过size属性获取。Object的键值对个数只能手动计算。
    迭代Map是可迭代的的,所以可以直接被迭代。

    Object没有实现迭代协议,所以使用 JavaSctipt 的 for...of 表达式并不能直接迭代对象。

    备注:

    • 对象可以实现迭代协议,或者你可以使用Object.keysObject.entries
    • for...in 表达式允许你迭代一个对象的可枚举属性。
    性能

    在频繁增删键值对的场景下表现更好。

    在频繁添加和删除键值对的场景下未作出优化。

    序列化和解析

    没有元素的序列化和解析的支持。

    (但是你可以使用携带replacer参数的JSON.stringify()创建一个自己的对Map的序列化和解析支持。参见 Stack Overflow 上的提问:How do you JSON.stringify an ES6 Map?)

    原生的由Object到 JSON 的序列化支持,使用JSON.stringify()

    原生的由 JSON 到Object的解析支持,使用JSON.parse()


    设置对象属性

    设置对象属性同样适用于 Map 对象,但容易造成困扰。

    即,以下的代码能够正常运行(但不推荐):

    const wrongMap = new Map();
    wrongMap['bla'] = 'blaa';
    wrongMap['bla2'] = 'blaaa2';
    
    console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }
    

    但这种设置属性的方式不会改变 Map 的数据结构。它使用的是通用对象的特性。'bla'的值未被存储在 Map 中,无法被查询到。其它的对这一数据的操作也会失败:

    wrongMap.has('bla')    // false
    wrongMap.delete('bla') // false
    console.log(wrongMap)  // Map { bla: 'blaa', bla2: 'blaaa2' }
    

    正确的存储数据到 Map 中的方式是使用set(key, value)方法。

    const contacts = new Map()
    contacts.set('Jessie', {phone: "213-555-1234", address: "123 N 1st Ave"})
    contacts.has('Jessie') // true
    contacts.get('Hilary') // undefined
    contacts.set('Hilary', {phone: "617-555-4321", address: "321 S 2nd St"})
    contacts.get('Jessie') // {phone: "213-555-1234", address: "123 N 1st Ave"}
    contacts.delete('Raymond') // false
    contacts.delete('Jessie') // true
    console.log(contacts.size) // 1
    


    构造函数

    Map()

    创建Map对象。


    静态属性

    get Map[@@species]

    用于创建派生对象的构造函数。


    实例属性

    Map.prototype.size

    返回Map对象中的键值对数量。


    实例方法

    Map.prototype.clear()

    移除Map对象中所有的键值对。

    Map.prototype.delete()

    移除Map对象中指定的键值对,如果键值对存在并成功被移除,返回true,否则返回false。调用delete后再调用map.has(key)将返回false

    Map.prototype.get()

    返回与指定的键key关联的值,若不存在关联的值,则返回undefined

    Map.prototype.has()

    返回一个布尔值,用来表明Map对象中是否存在与指定的键key关联的值。

    Map.prototype.set()

    Map对象中设置与指定的键key关联的值,并返回Map对象。

    Map.prototype[@@iterator]()

    返回一个新的迭代对象,其为一个包含Map对象中所有键值对的[key, value]数组,并以插入Map对象的顺序排列。

    Map.prototype.keys()

    返回一个新的迭代对象,其中包含Map对象中所有的键,并以插入Map对象的顺序排列。

    Map.prototype.values()

    返回一个新的迭代对象,其中包含Map对象中所有的值,并以插入Map对象的顺序排列。

    Map.prototype.entries()

    返回一个新的迭代对象,其为一个包含Map对象中所有键值对的[key, value]数组,并以插入Map对象的顺序排列。

    Map.prototype.forEach()

    以插入的顺序对Map对象中存在的键值对分别调用一次callbackFn。如果给定了thisArg参数,这个参数将会是回调函数中this的值。


    示例

    使用 Map 对象

    const myMap = new Map();
    
    const keyString = 'a string';
    const keyObj = {};
    const keyFunc = function() {};
    
    // 添加键
    myMap.set(keyString, "和键'a string'关联的值");
    myMap.set(keyObj, "和键 keyObj 关联的值");
    myMap.set(keyFunc, "和键 keyFunc 关联的值");
    
    console.log(myMap.size); // 3
    
    // 读取值
    console.log(myMap.get(keyString)); // "和键'a string'关联的值"
    console.log(myMap.get(keyObj)); // "和键 keyObj 关联的值"
    console.log(myMap.get(keyFunc)); // "和键 keyFunc 关联的值"
    
    console.log(myMap.get('a string')); // "和键'a string'关联的值",因为 keyString === 'a string'
    console.log(myMap.get({})); // undefined,因为 keyObj !== {}
    console.log(myMap.get(function() {})); // undefined,因为 keyFunc !== function () {}
    


    将 NaN 作为 Map 的键

    NaN也可以作为Map对象的键。虽然NaN与任何值甚至与自己都不相等(NaN !== NaN返回 true),但是因为所有的NaN的值都是无法区分的,所以下面的例子成立:

    const myMap = new Map();
    myMap.set(NaN, 'not a number');
    
    myMap.get(NaN);
    // "not a number"
    
    const otherNaN = Number('foo');
    myMap.get(otherNaN);
    // "not a number"
    


    使用 for...of 方法迭代 Map

    Map可以使用for...of循环来实现迭代:

    const myMap = new Map();
    myMap.set(0, 'zero');
    myMap.set(1, 'one');
    
    for (const [key, value] of myMap) {
      console.log(`${key} = ${value}`);
    }
    // 0 = zero
    // 1 = one
    
    for (const key of myMap.keys()) {
      console.log(key);
    }
    // 0
    // 1
    
    for (const value of myMap.values()) {
      console.log(value);
    }
    // zero
    // one
    
    for (const [key, value] of myMap.entries()) {
      console.log(`${key} = ${value}`);
    }
    // 0 = zero
    // 1 = one
    


    使用 forEach()方法迭代 Map

    Map也可以通过forEach()方法迭代:

    myMap.forEach((value, key) => {
      console.log(`${key} = ${value}`);
    });
    // 0 = zero
    // 1 = one
    


    Map 与数组的关系

    const kvArray = [['key1', 'value1'], ['key2', 'value2']];
    
    // 使用常规的 Map 构造函数可以将一个二维键值对数组转换成一个 Map 对象
    const myMap = new Map(kvArray);
    
    console.log(myMap.get('key1')); // "value1"
    
    // 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
    console.log(Array.from(myMap)); // 输出和 kvArray 相同的数组
    
    // 更简洁的方法来做如上同样的事情,使用展开运算符
    console.log([...myMap]);
    
    // 或者在键或者值的迭代器上使用 Array.from,进而得到只含有键或者值的数组
    console.log(Array.from(myMap.keys())); // 输出 ["key1", "key2"]
    


    复制或合并 Maps

    Map能像数组一样被复制:

    const original = new Map([
      [1, 'one'],
    ]);
    
    const clone = new Map(original);
    
    console.log(clone.get(1)); // one
    console.log(original === clone); // false. 浅比较 不为同一个对象的引用
    

    备注:请记住,数据本身未被克隆。

    Map对象间可以进行合并,但是会保持键的唯一性。

    const first = new Map([
      [1, 'one'],
      [2, 'two'],
      [3, 'three'],
    ]);
    
    const second = new Map([
      [1, 'uno'],
      [2, 'dos']
    ]);
    
    // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的。
    // 展开语法本质上是将 Map 对象转换成数组。
    const merged = new Map([...first, ...second]);
    
    console.log(merged.get(1)); // uno
    console.log(merged.get(2)); // dos
    console.log(merged.get(3)); // three
    

    Map对象也能与数组合并:

    const first = new Map([
      [1, 'one'],
      [2, 'two'],
      [3, 'three'],
    ]);
    
    const second = new Map([
      [1, 'uno'],
      [2, 'dos']
    ]);
    
    // Map 对象同数组进行合并时,如果有重复的键值,则后面的会覆盖前面的。
    const merged = new Map([...first, ...second, [1, 'eins']]);
    
    console.log(merged.get(1)); // eins
    console.log(merged.get(2)); // dos
    console.log(merged.get(3)); // three
    

    下篇:new Map()