# 十二、映射和集合

`Maps`和`Sets`是对 JavaScript 的欢迎。`Sets`和`Maps`为常用算法提供了高效的数据结构。

在 JavaScript 中，将对象或基元值从/映射到其他对象或基元值一直是一个难题。在 ES5 中，你能做的最好的事情就是从字符串映射到任意值。ES6 中的`Map`数据结构允许您使用任意值作为关键字。

让我们看一个简单的例子:

代码清单 159

```js
  let map = new Map();

  map.set('foo', 123);
  console.log(map.get('foo'));
  console.log(map.has('foo'));

  console.log(map.delete('foo'));
  console.log(map.has('foo'));

```

下面是前面代码的输出:

代码清单 160

```js
  123
  true
  true
  false

```

让我们看另一个例子:

代码清单 161

```js
  let map = new Map();
  map.set('foo', true);
  map.set('bar', false);

  console.log(map.size);
  map.clear();
  console.log(map.size);

```

下面是前面代码的输出:

代码清单 162

```js
  2
  0

```

您可以通过可重复的键值对来设置映射。一种方法是使用如下数组:

代码清单 163

```js
  let map = new Map([

  [1, 'one'],

  [2, 'two'],

  [3, 'three']
  ]);

```

另一种方法是这样连锁集合方法:

代码清单 164

```js
  let map = new Map()

  .set(1, 'one')

  .set(2, 'two')

  .set(3, 'three');

```

我们在遍历地图时有几个选项。首先，考虑以下地图定义:

代码清单 165

```js
  let map = new Map([

  [1, 'one'],

  [2, 'two'],

  [3, 'three']
  ]);

```

现在，让我们看看如何迭代这张地图。第一个示例将遍历这些键:

代码清单 166

```js
  for (let key of
  map.keys()) {

  console.log(key);
  }    

```

我们还可以迭代这些值:

代码清单 167

```js
  for (let value
  of map.values()) {

  console.log(value);
  }

```

我们可以遍历条目:

代码清单 168

```js
  for (let entry
  of map.entries()) {

  console.log(entry[0], entry[1]);
  }

```

我们可以获得条目的键/值对:

代码清单 169

```js
  for (let [key,
  value] of map.entries()) {

  console.log(key, value);
  }

```

最后，因为 entries 本身就是一个迭代器，我们可以用更简洁的方式重写前面的:

代码清单 170

```js
  for (let [key,
  value] of map) {

  console.log(key, value);
  }

```

`spread`运算符(…)将可迭代函数转换为函数或参数调用的参数。我们在遍历地图时有几个选项。因此，我们可以像下面这样轻松地展开地图:

代码清单 171

```js
  let map = new Map([

  [1, 'one'],

  [2, 'two'],

  [3, 'three']
  ]);
  let arr =
  [...map.keys()];
  console.log(arr);

```

上述代码生成以下输出:

代码清单 172

```js
  [ 1, 2, 3 ]

```

A `WeakMap`是一张不妨碍其按键被垃圾回收的地图。这意味着您可以将数据与对象相关联，而不用担心内存泄漏。

A `WeakMap`是一种数据结构，其键必须是对象，其值可以是任意值。它有和`Map`一样的 API，有一个显著的区别:你不能迭代内容。您不能迭代键、值或条目。你也清除不了一个`WeakMap`。

让我们看看下面的例子:

代码清单 173

```js
  let _counter = new WeakMap();
  let _action = new WeakMap();

  class Countdown {

  constructor(counter, action) {

  _counter.set(this, counter);

  _action.set(this, action);

  }

  dec() {

  let counter = _counter.get(this);

  if (counter < 1) return;

  counter--;

  _counter.set(this, counter);

  if (counter === 0) {

  _action.get(this)();

  }

  }
  }

  let c = new Countdown(2,
  () => console.log('DONE'));
  c.dec();
  c.dec();

```

前面的代码使用`WeakMaps` `_counter`和`_action`来存储私有数据。这是非常优雅的，因为这些`WeakMaps`正在“持有”对`Countdown`类的引用，然而它们是以一种弱的方式这样做的，因此是内存泄漏安全的。

上述代码生成以下输出:

代码清单 174

```js
  DONE

```

`Set`处理任意值，速度快，甚至能正确处理`NaN`。

让我们看一个例子:

代码清单 175

```js
  let set = new Set();
  set.add('red');

  console.log(set.has('red'));
  set.delete('red');
  console.log(set.has('red'));

```

下面是前面代码的输出:

代码清单 176

```js
  true
  false

```

让我们看另一个例子:

代码清单 177

```js
  let set = new Set();
  set.add('red');
  set.add('green');

  console.log(set.size);
  set.clear();
  console.log(set.size);

```

下面是前面代码的输出:

代码清单 178

```js
  2
  0

```

您可以通过对构成集合的元素进行迭代来设置集合。一种方法是使用如下数组:

代码清单 179

```js
  let set = new Set(['red',
  'green', 'blue']);

```

另一种方法是这样连锁集合方法:

代码清单 180

```js
  let set = new Set()
       .add('red')
       .add('green')
       .add('blue');

```

像地图一样，元素的比较类似于`===`，除了`NaN`像任何其他值。请考虑以下几点:

代码清单 181

```js
  let set = new Set([NaN]);
  console.log(set.size);
  console.log(set.has(NaN));

```

执行前面的代码会产生以下输出:

代码清单 182

```js
  1
  true

```

集合是可迭代的，并且`for-of`循环的工作方式与您预期的完全一样。考虑以下示例:

代码清单 183

```js
  let set = new Set(['red',
  'green', 'blue']);
  for (let x of
  set) {

  console.log(x);
  }

```

执行前面的代码会产生以下输出:

代码清单 184

```js
  red
  green
  blue

```

设置保留迭代顺序。也就是说，元素总是按照插入的顺序迭代。

现在，让我们看一个使用扩展运算符(`…`)的例子。正如您所知，它使用 iterables，因此允许您将集合转换为数组:

代码清单 185

```js
  let set = new Set(['red',
  'green', 'blue']);
  let arr = [...set];
  console.log(arr);

```

执行前面的代码会产生以下输出:

代码清单 186

```js
  [ 'red', 'green', 'blue' ]

```

从数组到集合怎么样？看看下面的例子:

代码清单 187

```js
  let arr = [3, 5, 2, 2, 5, 5];
  let unique = [...new Set(arr)];
  for (let x of
  unique) {

  console.log(x);
  }

```

上述代码产生以下输出:

代码清单 188

```js
  3
  5
  2

```

您可能已经注意到，集合中没有重复的值。

相比数组，集合没有`map()`和`filter()`这两种方法。但是，您可以做一个简单的技巧，将它们转换成数组，然后再转换回来。考虑以下示例:

代码清单 189

```js
  let set = new Set([1,
  2, 3]);
  set
  = new
  Set([...set].map(x => x * 2));
  // Resulting set: {2, 4, 6}

```

下面是一个使用过滤器的例子:

代码清单 190

```js
  let set = new Set([1,
  2, 3, 4, 5]);
  set
  = new
  Set([...set].filter(x => (x % 2) == 0));
  // Resulting set: {2, 4}

```

A `WeakSet`是不阻止其元素被垃圾收集的集合。A `WeakSet`不允许迭代、循环或清除。

假设你不能迭代它们的元素，那么`WeakSet`的用例就不多了。它们使你能够标记对象，并把它们与布尔值联系起来。

WeakSets 只有三种方法，它们的工作原理都与`set`方法相同:

*   `add(value)`
*   `has(value)`
*   `delete(value)`