Set

基本概念

Set用于存放任何类型的对象或值,内部元素的值都是唯一的

Set本身是一个构造函数,可以用new生成新的Set数据结构,可以接受数组或类数组(具有iterable接口)为参数

1
2
let s = new Set([1, 1, 2, 2, 3, 3, 4, 4]);
console.log(s) // => Set { 1, 2, 3, 4 }

通过Array.from可以将Set转换为数组

1
2
let s = new Set([1, 2, 3, 4]);
let arr = Array.from(s); // => [ 1, 2, 3, 4 ]

可以通过将数组转换为Set在转为数组来进行去重

1
2
let arr = [1, 1, 2, 2, 3, 3, 4, 4];
arr = Array.from(new Set(arr)); // => [ 1, 2, 3, 4 ]

有两个需要注意的地方:

  • Set中两个NaN是相等的

    1
    2
    let s = new Set([NaN, NaN]);
    s // => Set { NaN }
  • Set中两个对象总是不相等的,即使它们都是空对象

    1
    2
    let s = new Set([{}, {}]);
    s // => Set { {}, {} }

    如果是相同的引用则算作重复

    1
    2
    3
    let a = {}, b = a;
    let s = new Set([a, b]);
    s // => Set { {} }

实例的属性

  • Set.prototype.constructor

    返回实例的构造函数。默认情况下是Set

  • Set.prototype.size

    返回Set对象的值的个数

    1
    2
    let s = new Set([1, 1, 2, 2, 3, 3, 4, 4]);
    s.size // => 4

实例的方法

操作方法

  • Set.prototype.add(value)

    Set尾部添加值,返回原Set;支持链式操作,添加重复的值无效

    1
    2
    3
    4
    5
    let s = new Set();

    s.add(1); // => Set { 1 }
    s.add(2).add(3); // => Set { 1, 2, 3 }
    s.add(2); // => Set { 1, 2, 3 }

    Set加入值时不会发生类型转换

    1
    2
    let s = new Set();
    s.add(1).add('1'); // => Set { 1, '1' }
  • Set.prototype.delete(value)

    删除Set中的某个值,如果存在返回true,否则返回false

    1
    2
    3
    let s = new Set([1, 2, 3, 4]);
    s.delete(2); // => Set { 1, 3, 4 }
    console.log(s.delete(2)); // => false
  • Set.prototype.has(value)

    判断某个值是否在Set中,返回一个布尔值

    1
    2
    3
    let s = new Set([1, 2, 3, 4]);
    console.log(s.has(2)); // => true
    console.log(s.has('2')); // => false
  • Set.prototype.clear()

    清空Set中所有值

    1
    2
    let s = new Set([1, 2, 3, 4]);
    s.clear(); // => Set {}

遍历方法

  • values()keys()entries()

    Set结构没有键名,只有键值,故values()keys()方法行为一致,返回遍历器对象,按照插入顺序遍历Set的值;entries()返回的遍历器同时包含键名和键值,键名用键值代替

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const s = new Set([1, 'a', '1']);

    for (let item of s.keys()) {
    console.log(item);
    }
    // 1 'a' '1'
    for (let item of s.values()) {
    console.log(item);
    }
    // 1 'a' '1'
    for (let item of s.entries()) {
    console.log(item);
    }
    // [ 1, 1 ] [ 'a', 'a' ] [ '1', '1' ]
  • forEach()

    Set的每个值执行某种操作

    1
    2
    3
    const s = new Set([1, 'a', '1']);
    s.forEach((value, key) => console.log(value + key));
    // 2 aa 11
  • 扩展运算符

    扩展运算符可以用于Set

    1
    2
    let arr = [1, 1, 2, 2, 3, 3, 4, 4];
    arr = [...new Set(arr)]; // => [ 1, 2, 3, 4 ]

    可以通过将Set转换为数组从而使用数组实例的方法,再将数组转回Set来达到修改的目的

    1
    2
    let s = new Set([1, 2, 3]);
    s = new Set([...s].map(x => x * 2)); // => Set { 2, 4, 6 }

WeakSet

WeakSet结构与Set类似,但有两个不同

  • WeakSet的成员只能是对象

    1
    2
    3
    4
    let ws = new WeakSet();

    ws.add(1);
    // TypeError: Invalid value used in weak set
  • WeakSet中的对象都是弱引用,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象是否还存在于WeakSet

    由于这种特性,WeakSet无法遍历,可以用于存储DOM节点,而不用担心这些节点从文档移除时会引发内存泄漏

Map

基本概念

Map是键值对的集合,但是键不一定是字符串,也可以是各种类型的值

Map本身是一个构造函数,可以用new生成新的Map数据结构,可以接受数组或类数组(具有iterable接口)为参数,其元素为键值对(两个元素的数组),null会被当做undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let m = new Map([
['name', 'apple'],
['type', 'fruit'],
['price']
]);

console.log(m);
// Map { 'name' => 'apple', 'type' => 'fruit', 'price' => undefined }
----------------------------------------------------------------------
let s = new Set([
['hello', 'world']
]);
let m = new Map(s);

console.log(m);
// Map { 'hello' => 'world' }

实例的属性

  • Map.prototype.constructor

    返回一个函数,它创建了实例的原型。默认是Map函数

  • Map.prototype.size

    返回Map对象的键/值对的数量

    1
    2
    3
    4
    5
    6
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    m.size // => 2

实例的方法

操作方法

  • Map.prototype.set(key, value)

    设置Map中的键值,返回该Map对象;如果key已有值,则会被更新;支持链式操作

    1
    2
    3
    4
    5
    6
    7
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    m.set('price', '$1').set('name', 'banana');
    // Map { 'name' => 'banana', 'type' => 'fruit', 'price' => '$1' }

    需要注意的是,只有对同一个对象的引用,Map才将其视为同一个键

    1
    2
    3
    4
    5
    let key1 = ['a'], key2 = ['a'], key3 = key1;

    let m = new Map();
    m.set(key1, 1).set(key2, 2).set(key3, 3);
    // Map { [ 'a' ] => 3, [ 'a' ] => 2 }

    如果Map的键是简单类型(数字、字符串、布尔值),那么只要两个值严格相等,Map就将其视为一个键;特别地,NaN也被视为同一个键

    1
    2
    3
    4
    5
    6
    7
    let m = new Map();
    m.set(-0, 0).set(+0, 1);
    m.set(true, 0).set('true', 1);
    m.set(NaN, 0).set(NaN, 1);

    console.log(m)
    // Map { 0 => 1, true => 0, 'true' => 1, NaN => 1 }
  • Map.prototype.get(key)

    返回键对应的值,如果不存在,则返回undefined

    1
    2
    3
    4
    5
    6
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    m.get('name') // => apple
  • Map.prototype.has(key)

    返回一个布尔值,表示Map实例是否包含键对应的值

    1
    2
    3
    4
    5
    6
    7
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    m.has('name'); // => true
    m.has('price'); // => false
  • Map.prototype.clear()

    移除Map的所有键值对,无返回值

    1
    2
    3
    4
    5
    6
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    m.clear(); // => Map {}
  • Map.prototype.delete(key)

    如果Map对象中存在该元素,则移除它并返回true;否则如果该元素不存在则返回false

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    m.delete('name'); // => true
    m.delete('price'); // => false

    console.log(m); // => Map { 'type' => 'fruit' }

遍历方法

  • Map.prototype.keys()

    返回一个新的Iterator对象,按插入顺序包含了Map中每个元素的键

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    for (let key of m.keys()) {
    console.log(key);
    }
    // name type
  • Map.prototype.values()

    返回一个新的Iterator对象,按插入顺序包含了Map中每个元素的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    for (let value of m.values()) {
    console.log(value);
    }
    // apple fruit
  • Map.prototype.entries()

    返回一个新的Iterator对象,按插入顺序包含了Map中每个元素的[key, value]数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    for (let item of m.entries()) {
    console.log(item);
    }
    // [ 'name', 'apple' ] [ 'type', 'fruit' ]
  • Map.prototype.forEach(callbackFn)

    按插入顺序,为Map里的每一键值对调用一次callbackFn函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    m.forEach((key, value) => {
    console.log(`${key}: ${value}`)
    })
    // apple: name fruit: type

Set类似,Map结构也可以使用扩展运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const m = new Map([
['name', 'apple'],
['type', 'fruit'],
]);

[...m]
// [ [ 'name', 'apple' ], [ 'type', 'fruit' ] ]

[...m.keys()] // => [ 'name', 'type' ]

[...m.values()] // => [ 'apple', 'fruit' ]

[...m.entries()]
// [ [ 'name', 'apple' ], [ 'type', 'fruit' ] ]

Map与其他数据结构的转换

  • 数组

    Map转变为数组可以使用扩展运算符

    1
    2
    3
    4
    5
    6
    7
    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    let arr = [...m];
    // [ [ 'name', 'apple' ], [ 'type', 'fruit' ] ]

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

    1
    2
    3
    4
    5
    6
    7
    let arr = [
    ['name', 'apple'],
    ['type', 'fruit']
    ];

    let m = new Map(arr);
    // Map { 'name' => 'apple', 'type' => 'fruit' }
  • 对象

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function mapToObj(map) {
    let obj = new Object();
    map.forEach((key, value) => {
    obj[key] = value;
    });
    return obj;
    }

    let m = new Map([
    ['name', 'apple'],
    ['type', 'fruit'],
    ]);

    mapToObj(m)
    // { apple: 'name', fruit: 'type' }

    对象也可以转为Map

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function objToMap(obj) {
    let m = new Map();
    for (let key in obj) {
    m.set(key, obj[key]);
    }
    return m;
    }

    let obj = { apple: 'name', fruit: 'type' };

    objToMap(obj)
    // Map { 'apple' => 'name', 'fruit' => 'type' }
  • JSON

    通过将Map转换为数组或对象再转换为JSON

WeakMap

WeakMapMap类似,是一组键值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的

典型的应用场景是:在网页的DOM元素上添加数据时就可以使用WeakMap结构,当该元素被清除,其所对应的WeakMap记录就会自动被移除