「前端开发」 -

JS-ES6-07

ECMAScript6 —— Set&Map数据结构

Posted by eliochiu on November 10, 2022

Set

基本用法

ES6提供了一种新的数据结构——Set,它类似于数组,但成员的值都是唯一的,没有重复值。Set本身是一个构造函数,用来生成Set数据结构。

1
2
3
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
s // {2, 3, 5, 4}

Set构造函数可以接受一个数组:

1
2
const set = new Set([2, 3, 4, 3, 2]);
set // {2, 3, 4}

Set实例的属性和方法

属性

Set结构有如下的属性:

  • Set.prototype.constructor:构造函数,默认就是Set函数
  • Set.prototype.size:返回Set实例的成员总数。

Set的方法分为两类:操作方法和遍历方法。

操作方法

Set共有四个操作方法:

  • add(value):添加某个值,返回Set结构本身。
  • delete(value):删除某个值,返回一个布尔值代表是否删除成功。
  • has(value):返回一个布尔值,代表参数是否是Set的成员。
  • clear():清除所有成员,没有返回值。

上面这些属性和方法的实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
var s = new Set();

// 2被添加了两次
s.add(1).add(2).add(2); 

s.size; // 2
s.has(1); // true
s.has(2); // true
s.has(3); // false

s.delete(2);
s.has(2); // false

Array.from()可以将Set结构转为数组。

遍历方法

Set结构有四个遍历方法,可用于遍历成员:

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

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

keys()、values()、entries()

这三个方法返回的都是遍历器对象。由于Set结构没有键名,只有键值,因此keys(), values()的行为完全一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
    console.log(item);
}

// red
// green
// blue

for (let item of set.values()) {
    console.log(item);
}

// red
// green
// blue

for (let item of set.entries()) {
    console.log(item);
}

// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

Set结构默认遍历器就是values()

1
Set.prototype[Symbol.iterator] === Set.prototype.values; // true

因此可以直接使用for...of遍历Set结构。

forEach

forEach和数组用法类似,对每个成员执行某个操作,没有返回值:

1
2
3
4
5
let set = new Set([1, 2, 3]);
set.forEach((value, key) => console.log(value * 3));
// 2
// 4
// 6

遍历的应用

1
2
let set = new Set(['red', 'green', 'blue']);
let arr = [...set]; // ['red', 'green', 'blue']

数组去重:

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

filter和map:

1
2
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x ** 2)); // [1, 4, 9]

并集、交集、差集:

1
2
3
4
5
6
7
8
9
10
11
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]); // [1, 2, 3, 4]

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));

Map

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

为了解决这个问题,ES6提供了Map数据结构。它类似对象,但是各种类型的值都可以作为键。

1
2
3
4
5
6
7
8
9
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content');
m.get(o); // 'content'

m.has(o); // true
m.delete(o); // true
m.has(o); // false

Map构造函数可以接受数组作为参数,这个数组是一个键值对数组。

1
2
3
4
5
6
7
8
const map = new Map([
    ['name', '张三'],
    ['title', 'Author']
]);

map.size; // 2
map.has('name'); // true
map.get('name'); // '张三'

Map实例的属性和方法

size属性

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

1
2
3
4
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size; // 2

set(key, value)

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

1
2
3
4
const m = new Map();
map.set('edition', 6);
map.set(262, 'standard');
map.set(undefined, 'nah');

因为set直接返回新的Map结构,因而可以使用链式写法。

1
2
3
4
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');

get(key)

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

1
2
3
const m = new Map();
m.set('hello', 'Hello, ES6!');
m.get('hello'); // 'Hello, ES6!'

has(key)

has方法返回一个布尔值,用于判断Map结构是否有某个键值key

1
2
3
4
5
6
7
const m = new Map();
map.set('edition', 6);
map.set(262, 'standard');
map.set(undefined, 'nah');

map.has('edition'); // true
map.has('262'); // false

delete(key)

delete方法删除某个键,返回true; 若删除失败,返回false

1
2
3
4
5
6
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined); // true

m.delete(undefined);
m.has(undefined); //  false

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

1
2
3
4
5
6
7
let map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size; // 2
map.clear();  
map.size; // 0

遍历方法和Set一致,这里就不再赘述。

注意,Map方法默认的迭代器是entries(),因此可以使用let [key, value] of map直接遍历Map结构。(而Set默认方法为values())。

类型转换

Map转为数组

最容易的办法是使用扩展运算符(…)将Map转为数组:

1
2
3
const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);

[...myMap]; // [[true, 7], [{foo: 3}, ['abc']]]

数组转为Map

使用Map()构造函数,传入一个数组:

1
2
3
4
const myMap = new Map([
    [true: 7],
    [{foo: 3}, ['abc']]
]);

Map转为对象

如果Map所有键都是字符串,可以转化成对象。

1
2
3
4
5
6
7
8
9
10
function strMapToObj(strMap) {
    let obj = Object.create(null);
    for (let [k, v] of strMap) {
        obj[k] = v;
    }
    return obj;
}

const myMap = new Map().set('yes', true).set('no', false);
strMapToObj(myMap); // {yes: true, no: false}

对象转Map

1
2
3
4
5
6
7
8
function objToStrMap(obj) {
    let strMap = new Map();
    for (let p in obj) {
        // 也可以写成 for (let k of Object.keys(obj))
        strMap.set(p, obj[p]);
    } 
    return strMap;
}

Map转为JSON

Map的键名都是字符串,可以考虑转为对象JSON。具体步骤为:先将Map转为对象,再使用JSON.stringnify方法进行序列化。

1
2
3
function strMapToJson(strMap) {
    return JSON.stringnify(strMapToObj(strMap));
}

如果Map的键名有非字符串,可以考虑转为数组JSON:

1
2
3
function mapToArrayJson(map) {
    return JSON.stringnify([...map]);
}

JSON转为Map

一般情况下,JSON结构的键名均为字符串,因此可以先将JSON反序列化为对象,然后再转为Map

1
2
3
function jsonToStrMap(json) {
    return objToStrMap(JSON.parse(json));
}

若整个JSON就是一个数组,里面包含的所有元素都为数组的时候,可以直接使用构造函数:

1
2
3
function jsonToMap(json) {
    return new Map(JSON.parse(json));
}