ES6-Map和Set与iterable类型 本文于 392 天之前发表,文中内容可能已经过时。
一、Map(映射) 在ES6之前,JavaScript里通常用 普通对象(Object) 来存储键值对。
这样会导致很多问题:
键只能是字符串(或Symbol) ,实际上Number或者其他数据类型作为键也是非常合理的;无法保证插入顺序 (旧版JS对象不保证属性顺序)于是,ES6 引入了 Map —— 一个真正的键值对集合,支持任意类型的键 ,并且保持插入顺序 。
Map 是一种有序的键值对集合,具有极快的查找速度,允许任何类型的键,包括原始类型和对象。它比普通对象提供了更丰富的功能,尤其是在键值对管理方面。
1.1、特点 初始化Map需要一个二维数组或者直接初始化一个空Map。 键可以是任何类型(对象、函数、原始类型等)。 按插入顺序存储键值对。 支持 size 属性来获取 Map 的大小。 支持 set(), get(), has(), delete(), clear() 等方法。 1.2、创建 Map 1 2 3 4 5 6 7 8 9 10 const map = new Map ();const userMap = new Map ([ ['name' , 'Alice' ], [1 , '年龄' ], [true , '是否VIP' ] ]); console .log (userMap);
1.3、Map属性 Map.prototype.size: Map 结构的成员总数
1 2 3 4 5 const map = new Map ();map.set ('name' , 'Bob' ); map.set (1 , '数字键' ); map.set ({ id : 1 }, '对象键' ); console .log (map.size );
1.4、Map方法 Map 的方法都是继承于原型,Map.prototype.set(key, value)
set(key, value) :设置键值名,返回整个 Map 结构get(key): 读取key对应的键值,如果找不到key,返回undefinedhas(key): 表示某个键是否在当前 Map 对象之中delete(key): 成功删除某个键,返回true,否则返回 falseclear(): 清除所有成员,没有返回值1)添加/修改键值(set) 1 2 3 4 5 const map = new Map ();map.set ('name' , 'Bob' ); map.set (1 , '数字键' ); map.set ({ id : 1 }, '对象键' ); console .log (map)
2)获取值(get) 1 2 3 console .log (map.get ('name' )); console .log (map.get (1 )); console .log (map.get ({ id : 1 }));
1 2 3 4 const obj = { id : 1 }const map = new Map ();map.set (obj, '对象键' ); console .log (map.get (obj);
3)删除键值(delete) 1 2 console .log (map.delete ('name' )) console .log (map.delete ('不存在的键' ))
4)检查键是否存在(has) 1 2 console .log (map.has (1 )); console .log (map.has ('age' ));
5)清空 Map(clear) 1 2 map.clear (); console .log (map);
1.5、Map遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const userMap = new Map ([ ['name' , 'Alice' ], ['age' , 25 ], ['isVIP' , true ] ]) userMap.forEach ((value, key ) => { console .log (`${key} : ${value} ` ) }) for (const [key, value] of userMap) { console .log (key, value) }
1.6、将 Map 结构转换为数组结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const map = new Map ([ [1 , 'one' ], [2 , 'two' ], [3 , 'three' ], ]); [...map.keys ()] [...map.values ()] [...map.entries ()] [...map]
除了转换为数组,也可以跟其他数据结构互相转换,如
Map 转为对象,对象转 Map Map 转为 JSON, JSON 转 Map 1.7、实战场景 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 const cache = new Map ();function fetchData (url ) { if (cache.has (url)) { console .log ('从缓存读取:' , url); return Promise .resolve (cache.get (url)); } return fetch (url) .then (res => res.json ()) .then (data => { cache.set (url, data); return data; }); } fetchData ('/api/user' ).then (console .log );fetchData ('/api/user' ).then (console .log ); const items = ['apple' , 'banana' , 'apple' , 'orange' , 'banana' , 'apple' ];const counter = new Map ();for (const item of items) { counter.set (item, (counter.get (item) || 0 ) + 1 ); } console .log (counter);const user1 = { name : 'Alice' };const user2 = { name : 'Bob' };const lastLogin = new Map ();lastLogin.set (user1, '2024-06-01' ); lastLogin.set (user2, '2024-06-02' ); console .log (lastLogin.get (user1)); const orders = new Map ();orders.set (101 , '已支付' ); orders.set (102 , '待支付' ); orders.set (103 , '已取消' ); for (const [id, status] of orders) { console .log (`订单 ${id} 状态: ${status} ` ); } const actionMap = new Map ([ ['create' , () => console .log ('创建' )], ['update' , () => console .log ('更新' )], ['delete' , () => console .log ('删除' )], ]); function handleAction (type ) { const action = actionMap.get (type); if (action) action (); else console .log ('未知操作' ); } handleAction ('update' );
二、Set (集合) Set 类似于数组,但是成员的值都是唯一的,没有重复的值。
2.2、创建 Set 1 2 3 4 5 6 const set = new Set ();const fruits = new Set (['🍎' , '🍌' , '🍎' , '🍊' ]);console .log (fruits);
1.3、Set属性 Set.prototype.constructor:构造函数,默认就是Set函数。Set.prototype.size:返回Set实例的成员总数。1 2 const colors = new Set (['red' , 'green' , 'blue' ]);console .log (colors.size );
1.4、Set方法 Set 的方法都是继承于原型,Set.prototype.add(value)。
set(key, value) :设置键值名,返回整个 Map 结构get(key): 读取key对应的键值,如果找不到key,返回undefinedhas(key): 表示某个键是否在当前 Map 对象之中delete(key): 成功删除某个键,返回true,否则返回 falseclear(): 清除所有成员,没有返回值1)添加值(add) 1 2 3 4 5 const numbers = new Set ();numbers.add (1 ); numbers.add (2 ); numbers.add (2 ); console .log (numbers);
2)删除值(delete) 1 2 console .log (numbers.delete (1 )) console .log (numbers.delete (99 ))
3)检查键是否存在(has) 1 2 console .log (numbers.has (2 )); console .log (numbers.has (5 ));
4)清空 Map(clear) 1 2 numbers.clear (); console .log (numbers);
1.5、Set遍历 1 2 3 4 5 6 7 8 9 10 11 const letters = new Set (['a' , 'b' , 'c' ]);letters.forEach ((value ) => { console .log (value); }); for (const letter of letters) { console .log (letter); }
1.6、将 Set结构转换为数组结构 1 2 3 4 5 6 const letters = new Set (['a' , 'b' , 'c' ])console .log ([...letters.keys ()]) console .log ([...letters.values ()]) console .log ([...letters.entries ()]) console .log ([...letters]) console .log (Array .from (letters))
1.7、实战场景 1 2 3 let arr = [3 , 5 , 2 , 2 , 5 , 5 ];let unique = [...new Set (arr)];
三、iterable类型 遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。
具有iterable类型的集合可以通过新的for … of循环来遍历。
用for … of循环遍历集合,用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 var a = ['A' , 'B' , 'C' ]var s = new Set (['A' , 'B' , 'C' ])var m = new Map ([[1 , 'x' ], [2 , 'y' ], [3 , 'z' ]])for (var x of a) { console .log (x) } for (var v of s) { console .log (v) } for (var r of m) { console .log (r[0 ] + '=' + r[1 ]) }
你可能会有疑问,for … of循环和for … in循环有何区别?
for … in循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
当我们手动给Array对象添加了额外的属性后,for … in循环将带来意想不到的意外效果:
1 2 3 4 5 var a = ['A' , 'B' , 'C' ];a.name = 'Hello' ; for (var x in a) { console .log (x); }
for … in循环将把name包括在内,但Array的length属性却不包括在内。
for … of循环则完全修复了这些问题,它只循环集合本身的元素:
1 2 3 4 5 var a = ['A' , 'B' , 'C' ];a.name = 'Hello' ; for (var x of a) { console .log (x); }
这就是为什么要引入新的for … of循环。
然而,更好的方式是直接使用iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数。以Array为例:
1 2 3 4 5 6 7 8 9 10 var a = ['A' , 'B' , 'C' ];a.forEach (function (element, index, array ) { console .log (element + ', index = ' + index); });
注意,forEach()方法是ES5.1标准引入的,你需要测试浏览器是否支持。
Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身:
1 2 3 4 5 var s = new Set (['A' , 'B' , 'C' ])s.forEach ((element, sameElement, set ) => { console .log (element) console .log (sameElement) })
Map的回调函数参数依次为value、key和map本身:
1 2 3 4 5 var m = new Map ([[1 , 'x' ], [2 , 'y' ], [3 , 'z' ]])m.forEach ((value, key, map ) => { console .log (value) console .log (key) })