11
22# Map and Set(映射和集合)
33
4- 我们已经了解过以下复杂数据结构:
4+ 我们已经了解了以下复杂的数据结构:
55
6- - 存储键集合的对象.
7- - 存储有序集合的数组.
6+ - 存储键集合的对象。
7+ - 存储有序集合的数组。
88
9- 但在我们的开发中并它不能满足需求 。这就是为什么 ` Map ` 和 ` Set ` 也存在的原因.
9+ 但现实生活中远不止这些 。这就是为什么 ` Map ` 和 ` Set ` 也存在的原因。
1010
1111## Map
1212
13- [ Map] ( mdn:js/Map ) 是一个含有数据的键的集合, 跟普通的 ` Object ` 一样. 但是它们最大的差别是 ` Map ` 允许键是任何类型 。
13+ [ Map] ( mdn:js/Map ) 是一个带键的数据集合, 跟普通的 ` Object ` 一样。 但是它们最大的差别是 ` Map ` 允许键(key)是任何类型 。
1414
15- 它的方法和属性如下:
15+ 它的方法和属性如下:
1616
17- - ` new Map() ` - 创建一个空集合 。
18- - ` map.set(key, value) ` - 存储含有值的键 。
19- - ` map.get(key) ` - 根据键来返回值, 如果 ` key ` 不在 ` map ` 里将会返回 ` undefined ` 。
20- - ` map.has(key) ` - 如果 ` key ` 存在则返回 ` true ` , 否则返回 ` false ` 。
21- - ` map.delete(key) ` - 根据键来删除值。
22- - ` map.clear() ` - 清空集合。
23- - ` map.size ` - 返回当前全部元素的数量 。
17+ - ` new Map() ` - 创建 ` Map ` 。
18+ - ` map.set(key, value) ` - 存储键值对 。
19+ - ` map.get(key) ` - 根据键来返回值, 如果 ` key ` 不在 ` map ` 里将会返回 ` undefined ` 。
20+ - ` map.has(key) ` - 如果 ` key ` 存在则返回 ` true ` , 否则返回 ` false ` 。
21+ - ` map.delete(key) ` - 根据键来删除值。
22+ - ` map.clear() ` - 清空集合。
23+ - ` map.size ` - 返回当前的元素个数 。
2424
25- 举个例子:
25+ 举个例子:
2626
2727``` js run
2828let map = new Map ();
@@ -31,80 +31,79 @@ map.set('1', 'str1'); // 字符串键
3131map .set (1 , ' num1' ); // 数字键
3232map .set (true , ' bool1' ); // 布尔值键
3333
34- // 还记得常规对象吗? 它会把键转化为字符串
35- // Map 会保持类型, 所以下面这两个结果不同:
34+ // 还记得普通的 Object 对象吗? 它会将键转化为字符串
35+ // Map 则会保留类型, 所以下面这两个结果不同:
3636alert ( map .get (1 ) ); // 'num1'
3737alert ( map .get (' 1' ) ); // 'str1'
3838
3939alert ( map .size ); // 3
4040```
4141
42- 如我们所见,与对象不同,键(key)不会转换为字符串 。键可以是任何类型。
42+ 如我们所见,与对象不同,键不会转换为字符串 。键可以是任何类型。
4343
4444** Map 还可以使用对象作为键**
4545
46- 例如:
46+ 例如:
4747
4848``` js run
4949let john = { name: " John" };
5050
51- // 我们先存储每个游客的来访次数
51+ // 存储每个用户的来访次数
5252let visitsCountMap = new Map ();
5353
54- // john 已经在集合里了
54+ // john 是集合中的键
5555visitsCountMap .set (john, 123 );
5656
5757alert ( visitsCountMap .get (john) ); // 123
5858```
5959
6060使用对象作为键是 ` Map ` 最值得注意和重要的功能之一。对于字符串键,` Object ` (普通对象)能正常使用,但对于对象键则不能。
6161
62- 我们来尝试一下:
62+ 我们来尝试一下:
6363
6464``` js run
6565let john = { name: " John" };
6666
67- let visitsCountObj = {}; // try to use an object
67+ let visitsCountObj = {}; // 尝试使用对象
6868
69- visitsCountObj[john] = 123 ; // try to use john object as the key
69+ visitsCountObj[john] = 123 ; // 尝试将 john 对象作为键
7070
7171* ! *
72- // That's what got written !
72+ // 是写成了这样 !
7373alert ( visitsCountObj[" [object Object]" ] ); // 123
7474*/ ! *
7575```
7676
77- 当 ` visitsCountObj ` 是一个普通对象的时候, 它会转化所有的键, ` john ` 被转化为字符串, 所以我们得到字符键 ` "[object Object]" ` . 很明显这不是我们要的结果.
77+ 因为 ` visitsCountObj ` 是一个普通对象,它会将所有的键如 ` john ` 转换为字符串, 所以我们得到字符键 ` "[object Object]" ` 。这显然不是我们想要的结果。
7878
7979```smart header="` Map ` 是怎么比较键的?"
8080
81- 为了测试键的一致性, ` Map ` 使用 [ SameValueZero] ( https://tc39.github.io/ecma262/#sec-samevaluezero ) 算法。 它大致上使用严格全等 ` === ` , 但不同的是 ` NaN ` 被看成是等于 ` NaN ` 。 所以 ` NaN ` 也可以被用作键.
81+ ` Map ` 使用 [ SameValueZero] ( https://tc39.github.io/ecma262/#sec-samevaluezero ) 算法来比较键是否相等。 它和严格等于 ` === ` 差不多,但区别是 ` NaN ` 被看成是等于 ` NaN ` 。 所以 ` NaN ` 也可以被用作键。
8282
8383这个算法不能被改变或者自定义。
8484
8585```
8686
8787````smart header="链式调用"
8888
89- 每一次调用 `map.set` 都会返回集合本身, 所以我们可以使用:
89+ 每一次调用 `map.set` 都会返回集合本身,所以我们可以”连续“调用:
9090
9191```js
9292map.set('1', 'str1')
9393 .set(1, 'num1')
9494 .set(true, 'bool1');
9595```
96- ````
9796
9897
9998## ` Map ` 迭代
10099
101- 如果要在 `map` 里使用循环, 可以使用以下三个方法:
100+ 如果要在 ` map ` 里使用循环, 可以使用以下三个方法:
102101
103- - `map.keys()` - 返回键名的遍历器,
104- - `map.values()` - 返回键值的遍历器,
105- - `map.entries()` - 返回实体 `[key, value]` 的遍历器,默认在 `for..of` 中使用。
102+ - ` map.keys() ` - 返回键名的遍历器,
103+ - ` map.values() ` - 返回键值的遍历器,
104+ - ` map.entries() ` - 返回实体 ` [key, value] ` 的遍历器,默认在 ` for..of ` 中使用。
106105
107- 例如:
106+ 例如:
108107
109108``` js run
110109let recipeMap = new Map ([
@@ -118,34 +117,34 @@ for (let vegetable of recipeMap.keys()) {
118117 alert (vegetable); // cucumber, tomatoes, onion
119118}
120119
121- // 迭代值 ( amounts)
120+ // 迭代值( amounts)
122121for (let amount of recipeMap .values ()) {
123122 alert (amount); // 500, 350, 50
124123}
125124
126125// 迭代 [key, value] 对
127126for (let entry of recipeMap) { // 效果跟 recipeMap.entries() 相同
128- alert(entry); // cucumber,500 (and so on)
127+ alert (entry); // cucumber,500(以此类推)
129128}
130129```
131130
132- ```smart header="按顺序插入 "
131+ ``` smart header="使用插入顺序 "
133132
134- 迭代的顺序与插入键的顺序相同。 `Map` 会保持相同的顺序,不像普通 `Object` 不保证顺序.
133+ 迭代的顺序与键的插入顺序相同。不同于普通的 `Object`,`Map` 会保留键的插入顺序。
135134```
136135
137- 除此之外, `Map` 有个内建 `forEach` 方法, 跟 `Array` 一样:
136+ 除此之外, ` Map ` 有个内置的 ` forEach ` 方法, 跟 ` Array ` 类似:
138137
139138``` js
140- // 对每个 (key, value) 对运行 forEach 函数
139+ // 对每个 (key, value) 键值对运行 forEach 函数
141140recipeMap .forEach ( (value , key , map ) => {
142- alert(`${key}: ${value}`); // cucumber: 500 etc
141+ alert (` ${ key} : ${ value} ` ); // cucumber: 500 等
143142});
144143```
145144
146- ## Object.entries: 把对象转化为 `map`
145+ ## Object.entries: 把对象转化为 ` map `
147146
148- 当 `Map` 被创建之后, 我们可以传入带有键值对的数组 (或其它可迭代的对象) 来进行初始化, 像这样:
147+ 创建 ` Map ` 后, 我们可以传入带有键值对的数组 (或其它可迭代的对象) 来进行初始化, 像这样:
149148
150149``` js run
151150// 包含 [key, value] 对的数组
@@ -158,7 +157,7 @@ let map = new Map([
158157alert ( map .get (' 1' ) ); // str1
159158```
160159
161- 如果我们有个纯对象, 并且想利用这个纯对象来创建 `Map`, 可以使用内建方法 [Object.entries(obj)](mdn:js/Object/entries),它返回一个具有相同格式的并且带有键值对的数组对象 。
160+ 如果我们想从一个已有的 plain object 来创建 ` Map ` ,可以使用内置方法 [ Object.entries(obj)] ( mdn:js/Object/entries ) ,它返回一个对象的键值对数组,该数组符合 ` Map ` 集合的格式 。
162161
163162所以可以像下面这样利用一个对象来创建 ` map `
164163
@@ -180,9 +179,9 @@ alert( map.get('name') ); // John
180179
181180## Object.fromEntries:把 ` map ` 转化为对象
182181
183- 我们刚刚已经利用 `Object.entries(obj)` 把一个纯对象转化成 `Map`
182+ 我们刚刚已经利用 ` Object.entries(obj) ` 把一个 plain object 转化成 ` Map ` 。
184183
185- `Object.fromEntries` 方法的作用是相反的: 给定一个具有 `[key, value]` 对的数组, 它会根据给定数组生成对象:
184+ ` Object.fromEntries ` 方法的作用是相反的: 给定一个具有 ` [key, value] ` 对的数组, 它会根据给定数组生成对象:
186185
187186``` js run
188187let prices = Object .fromEntries ([
@@ -196,11 +195,11 @@ let prices = Object.fromEntries([
196195alert (prices .orange ); // 2
197196```
198197
199- 我们可以使用 `Object.fromEntries` 从 `Map` 中得到一个纯对象 。
198+ 我们可以使用 ` Object.fromEntries ` 从 ` Map ` 中得到一个 plain object 。
200199
201- 例如, 我们存了数据在 `Map` 中, 但是我们需要把它转给需要纯对象的第三方代码 。
200+ 例如,我们存了数据在 ` Map ` 中,但是我们需要把它传给需要 plain object 的第三方代码 。
202201
203- 我们来开始:
202+ 我们来开始:
204203
205204``` js run
206205let map = new Map ();
@@ -209,43 +208,43 @@ map.set('orange', 2);
209208map .set (' meat' , 4 );
210209
211210* ! *
212- let obj = Object.fromEntries(map.entries()); // make a plain object (*)
211+ let obj = Object .fromEntries (map .entries ()); // 生成一个 plain object (*)
213212*/ ! *
214213
215- // done !
214+ // 完成 !
216215// obj = { banana: 1, orange: 2, meat: 4 }
217216
218217alert (obj .orange ); // 2
219218```
220219
221- 调用 `map.entries()` 将返回含有键值对的数组, 这刚好是 `Object.fromEntries` 所需要的格式.
220+ 调用 ` map.entries() ` 将返回含有键值对的数组, 这刚好是 ` Object.fromEntries ` 所需要的格式。
222221
223- 我们可以把带 `(*)` 这一行变得更短:
222+ 我们可以把带 ` (*) ` 的这一行写得更短:
224223
225224``` js
226- let obj = Object.fromEntries(map); // omit .entries()
225+ let obj = Object .fromEntries (map); // 省掉 .entries()
227226```
228227
229- 上面的代码作用也是一样的, 因为 `Object.fromEntries` 需要一个可迭代对象作为参数, 而一定是数组. `map` 的标准迭代会返回跟 `map.entries()` 一样的键值对. 所以我们可以获得一个与 `Map` 一样具有键值对的纯对象 。
228+ 上面的代码作用也是一样的, 因为 ` Object.fromEntries ` 需要一个可迭代对象作为参数,而不一定是数组。 ` map ` 的标准迭代会返回跟 ` map.entries() ` 一样的键值对。 所以我们可以获得一个与 ` map ` 一样具有键值对的 plain object 。
230229
231230## Set
232231
233- `Set` 是一个特别的类型集合 - " 值的集合" (没有键 ),它的每一个值只出现一次。
232+ ` Set ` 是一个特殊类型的集合 - ” 值的集合“ (没有键 ),它的每一个值只出现一次。
234233
235- 它的主要方法如下:
234+ 它的主要方法如下:
236235
237- - `new Set(iterable)` - 创建一个 `set`, 如果提供一个 `iterable` 对象(通常是数组),将会从数组里面复制值到 `set` 里面去 。
238- - `set.add(value)` - 添加一个值,返回 set 本身
239- - `set.delete(value)` - 删除值, 如果 `value` 在调用的时候值存在则返回 `true` ,否则返回 `false`。
240- - `set.has(value)` - 如果 `value` 存在 set 里面则返回 `true`, 否则返回 `false`。
241- - `set.clear()` - 移除 set 里面的所有成员 。
242- - `set.size` - 返回元素数量 。
236+ - ` new Set(iterable) ` - 创建一个 ` set ` ,如果提供了一个 ` iterable ` 对象(通常是数组),将会从数组里面复制值到 ` set ` 中 。
237+ - ` set.add(value) ` - 添加一个值,返回 set 本身
238+ - ` set.delete(value) ` - 删除值, 如果 ` value ` 在调用的时候存在则返回 ` true ` ,否则返回 ` false ` 。
239+ - ` set.has(value) ` - 如果 ` value ` 在 set 中,返回 ` true ` , 否则返回 ` false ` 。
240+ - ` set.clear() ` - 清空集合 。
241+ - ` set.size ` - 返回元素的个数 。
243242
244- 它的主要特点是重复使用同一个值调用 `set.add(value)` 并不会发生什么改变。 这就是 `Set` 里面的每一个值只出现一次的原因。
243+ 它的主要特点是重复使用同一个值调用 ` set.add(value) ` 并不会发生什么改变。这就是 ` Set ` 里面的每一个值只出现一次的原因。
245244
246- 例如, 我们有游客来访, 需要记住他们每一个人. 但是已经访问过的旅客会导致重复记录. 每个游客必须只被 "counted" 一次.
245+ 例如,我们有客人来访,想记住他们每一个人。但是已经访问过的客人不应重复记录。每个访客必须只被“计数”一次。
247246
248- `Set` 可以帮助我们解决这个问题:
247+ ` Set ` 可以帮助我们解决这个问题:
249248
250249``` js run
251250let set = new Set ();
@@ -254,75 +253,76 @@ let john = { name: "John" };
254253let pete = { name: " Pete" };
255254let mary = { name: " Mary" };
256255
257- // visits, 一些访客来访好几次
256+ // visits, 一些访客来访好几次
258257set .add (john);
259258set .add (pete);
260259set .add (mary);
261260set .add (john);
262261set .add (mary);
263262
264- // set 只保留那个游客第一次来的次数
263+ // set 只保留单一值
265264alert ( set .size ); // 3
266265
267266for (let user of set) {
268- alert(user.name); // John (然后 Pete 最后是 Mary)
267+ alert (user .name ); // John (然后 Pete 和 Mary)
269268}
270269```
271270
272- 使用 `Set` 的场景可以是一个用户数组, 并且每次插入的时候检查重复的的代码也可以使用 [arr.find](mdn:js/Array/find)。但是这样会是性能变的更差 ,因为这个方法会遍历整个数组来检查每个元素。 `Set` 有更好的内部优化 - 独一无二的检查.
271+ 替代 ` Set ` 的场景可以是一个用户数组,用 [ arr.find] ( mdn:js/Array/find ) 在每次插入时检查是否重复。但是这样会使性能变差 ,因为这个方法会遍历整个数组来检查每个元素。` Set ` 内部对唯一性检查进行了更好的优化。
273272
274273## Set 迭代
275274
276- 我们可以在 `Set` 中使用 ` for..of`和 `forEach` 它们两者之一来循环 :
275+ 我们可以使用 ` for..of ` 或 ` forEach ` 来循环 ` Set ` 集合:
277276
278277``` js run
279278let set = new Set ([" oranges" , " apples" , " bananas" ]);
280279
281280for (let value of set) alert (value);
282281
283- // 跟 forEach 方法相同:
282+ // 跟 forEach 方法相同:
284283set .forEach ((value , valueAgain , set ) => {
285284 alert (value);
286285});
287286```
288287
289- 注意到一件有趣的事情 。`forEach` 的回调函数有三个参数:一个 `value`,然后是 *相同值* `valueAgain`,最后才是目标对象本身,的确 ,相同的值在参数里出现了两次。
288+ 注意一件有趣的事情 。` forEach ` 的回调函数有三个参数:一个 ` value ` ,然后是* 相同值* ` valueAgain ` ,最后才是目标对象。没错 ,相同的值在参数里出现了两次。
290289
291- 那是为了兼容 `Map` 在回调里传入 `forEach` 函数后有三个参数。 当然这看起来有点奇怪。但是这对在特定情况下比如使用 `Set` 代替 `Map`的时候有帮助,反之亦然。
290+ 这是为了兼容 ` Map ` 在回调里传入 ` forEach ` 函数后有三个参数。当然这看起来有点奇怪。但是这对在特定情况下比如使用 ` Set ` 代替 ` Map ` 的时候有帮助,反之亦然。
292291
293- 类似于 `Map`,在 `Set`里用于迭代的方法也被支持:
294- - `set.keys()` - 返回一个包含值的可迭代对象,
295- - `set.values()` - 跟 `set.keys()` 作用相同, 为了兼容 `Map`,
296- - `set.entries()` - 返回一个包含 `[value, value]` 对的可迭代对象 , 它的存在也是为了兼容 `Map`.
292+ ` Map ` 中用于迭代的方法在 ` Set ` 中也同样支持:
293+
294+ - ` set.keys() ` - 返回一个包含值的可迭代对象,
295+ - ` set.values() ` - 跟 ` set.keys() ` 作用相同,为了兼容 ` Map ` ,
296+ - ` set.entries() ` - 返回一个包含 ` [value, value] ` 对的可迭代对象,它的存在也是为了兼容 ` Map ` 。
297297
298298## 总结
299299
300- `Map` - 是一个键值集合.
300+ ` Map ` - 是一个键值对集合。
301301
302- 方法和属性如下:
302+ 方法和属性如下:
303303
304- - `new Map([iterable])` - 创建空的 map, 可选的带有 `[key,value]` 对的`iterable` ( 例如数组) 对象来进行初始化 。
305- - `map.set(key, value)` - 存储对应的键值。
306- - `map.get(key)` - 根据键来返回值, 如果键不存在 map 里就返回 `undefined`。
307- - `map.has(key)` - 如果 `key` 存在则返回 `true` , 否则返回 `false`。
308- - `map.delete(key)` - 删除指定键值 。
309- - `map.clear()` - 清空 map 。
310- - `map.size` - 返回当前全部元素的数量 。
304+ - ` new Map([iterable]) ` - 创建空的 map,可选择带有 ` [key,value] ` 对的 ` iterable ` ( 例如数组) 对象来进行初始化 。
305+ - ` map.set(key, value) ` - 存储对应的键值。
306+ - ` map.get(key) ` - 根据键来返回值, 如果键不存在 map 里就返回 ` undefined ` 。
307+ - ` map.has(key) ` - 如果 ` key ` 存在则返回 ` true ` , 否则返回 ` false ` 。
308+ - ` map.delete(key) ` - 删除指定键的值 。
309+ - ` map.clear() ` - 清空 map 。
310+ - ` map.size ` - 返回当前全部元素的个数 。
311311
312- 跟普通对象 `Object` 最大的不同点是:
312+ 跟 plain object ` Object ` 最大的不同点是:
313313
314- - 任何键,对象都可以被用作它的键 ,
315- - 有额外的方法, 和 `size` 属性。
314+ - 任何键、对象都可以作为键 ,
315+ - 有其他的便捷方法,如 ` size ` 属性。
316316
317- `Set` - 是一个独一无二的值的集合.
317+ ` Set ` - 是一组唯一值的集合。
318318
319- 方法和属性:
319+ 方法和属性:
320320
321- - `new Set([iterable])` - 创建空的 set , 可选的带有 `iterable` ( 例如数组) 对象来进行初始化。
322- - `set.add(value)` - 添加一个 value(如果存在则什么也不做), 返回 set 本身。
323- - `set.delete(value)` - 删除 value , 如果在调用的时候存在则返回 `true`, 否则返回 `false`。
324- - `set.has(value)` - 如果则返回 `true`, 否则返回 `false`。
325- - `set.clear()` - 清空 set 。
326- - `set.size` - 返回当前全部元素的数量 。
321+ - ` new Set([iterable]) ` - 创建空的 set,可选的 ` iterable ` ( 例如数组) 对象来进行初始化。
322+ - ` set.add(value) ` - 添加一个值(如果值不存在), 返回 set 本身。
323+ - ` set.delete(value) ` - 删除值,如果 ` value ` 在调用的时候存在则返回 ` true ` , 否则返回 ` false ` 。
324+ - ` set.has(value) ` - 如果值在集合中,则返回 ` true ` , 否则返回 ` false ` 。
325+ - ` set.clear() ` - 清空集合 。
326+ - ` set.size ` - 返回当前全部元素的个数 。
327327
328- 在 `Map` 和 `Set` 里迭代总是按照插入的顺序来执行的 ,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其顺序来获取元素。
328+ 在 ` Map ` 和 ` Set ` 中迭代总是按照插入的顺序来执行的 ,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其顺序来获取元素。
0 commit comments