Map结构以及Set结构的用法整理。
Set
ES6提供了新的数据结构Set,类似于数组,但成员的值都是唯一的,没有重复的值。Set
本身是一个构造函数,用来生成Set数据结构。例如:
1 | const s = new Set(); |
运行上面的例子,通过一个数组数据源向Set结构加入成员,结果表明Set结构不会添加重复的值。
Set()
函数可以接受一个数组(或者具有iterable结构的其他数据结构)作为参数,用来初始化:
1 | const s = new Set([1, 2, 3, 4, 3, 2, 1]); |
向Set加入值的时候,不会发生类型转换,所以5
和“5”
是两个不同的值。Set内部判断两个值是否不同,使用的算法叫做“Same-value-zero-equality”,它类似于精确相等运算符===
,主要的区别是NaN
等于自身,而精确相等运算符认为NaN
不等于自身。
1 | const s = new Set([NaN, NaN]); |
上面例子可以看到:
- 向Set添加两个NaN,只能加入一个,表明在Set内部两个NaN是相等的。
- 直接使用精确相等运算符判断两个NaN是不相等的。
- 要判断一个值是否是NaN只能使用方法
isNaN(x)
来判断。
需要注意的是,两个对象总是不相等的。例如:
1 | const s = new Set([{}, {}]); |
上面表明,两个空对象都被加入到了Set结构中。
Set实例的属性
- Set.prototype.constructor 构造函数,创建Set数据结构。
- Set.prototype.size 返回Set实例的成员总数
Set实例方法
Set实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。
四个操作方法
add(value)
添加某个值,返回Set结构本身。delete(value)
删除某个值,返回一个布尔值,表示删除是否成功。has(value)
返回一个布尔值,表示该值是否为Set的成员。clear()
清除所有的成员,没有返回值。
使用实例:
1 | const s = new Set(); |
Arrary.form
方法可以将Set结构转换为数组:
1 | const s = new Set([1, 2, 4, 3, 2, 1]); |
利用这一特性可以给数组去重:
1 | function dedupe(array) { |
四个遍历方法
keys()
返回键名的遍历器values()
返回键值的遍历器entries()
返回键值对的遍历器forEach()
使用回调函数遍历每个成员
特别说明的是,Set
的遍历顺序就是插入顺序。这个特性非常有用,比如使用Set
保存一个回调函数列表,调用时就能保证按照添加顺序调用。
keys()
,values()
,entries()
返回的都是遍历器对象,由于Set
结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys()
和value()
方法的行为完全一致。例如:
1 | const s = new Set([1, 2, 3]); |
entries
方法返回的遍历器同时包括键名和键值,所以每次输出一个数组,键名和键值完全相等。
Set结构可以默认遍历,它的默认遍历器生成的函数就是它的values()
方法:
1 | console.log(Set.prototype[Symbol.iterator]); // [Function: values] |
所以我们可以直接使用for...of
遍历Set:
1 | const s = new Set([1, 2, 3]); |
forEach()
方法用于对每个成员执行某种操作,没有返回值。该函数的参数与数组的forEach
一致,依次为键值、键名、集合本身。需要注意的是:Set结构的键值和键名是同一个值。因此第一个参数和第二个参数的值永远都是一样的。
另外,forEach()
还可以有第二个参数,表示绑定处理函数内部的this
对象。例如:
1 | const s = new Set([1, 2, 3]); |
遍历的应用
扩展运算符...
内部使用for...of
循环,所以也可以用于Set结构,扩展运算符和Set结构结合使用,可以简洁的给数组去重:
1 | const arr = [1, 2, 3, 1, 2]; |
数组的map
和filter
也可以间接用于Set,可以很容易的实现并集(union)、交集(intersect)和差集(difference):
1 | const arr1 = [1, 2, 3, 1, 2]; |
Map
JavaScript的对象(Object)本质上是键值对的集合(Hash结构)。但是传统上只能使用字符串当做键,这使得使用带来很大的限制。为了解决这个问题,ES6提供了Map数据结构,它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当做键。也就是说,Object结构提供了“字符串-值”的对应,Map结构提供了“值-值”的对应。是一种更完善的Hash结构实现。如果你需要“键值对”数据结构,Map比Object更合适。
Map
是一个构造函数,可以直接构造Map实例:
1
2
3
4
5
6
7const m = new Map([
['name', 'tony'],
['age', 18],
]);
console.log(m.size); // 2
console.log(m.get('name')); // tony
console.log(m.has('age')); // true
Map
构造函数在接受数组作为参数时,实际上是执行下面的过程:
1 | const items = [ |
不仅仅是数组,任何具有Iterator接口、且每个成员都是一个双元素的数组的数据结构都可以当做Map
构造函数的参数。也就是说,Set
和Map
都可以用来生成新的Map。
1 | const set = new Set([ |
Map 实例的属性
和Set结构一样,有两个属性:
- 构造方法,生成一个Map实例
- size属性,返回Map结构的成员总数
1 | const map = const set = new Set([ |
Map 实例的方法
操作方法
set(key, value)
给Map设置键名key
对象的键值value
,然后返回整个Map结构,如果key
已经有值,则键值会被更新,否则就生成该键值对。get(key)
读取key
对应的键值,如果找不到key
,返回undefined
。has(key)
返回一个布尔值,表示某个键是否在当前的Map对象中。delete(key)
删除某个键,删除成功为true,删除失败为false。clear()
清除所有成员,没有返回值。
对一个键多次赋值,后面的值覆盖前面的值:
1 | const map = new Map(); |
读取一个未知的键返回undefined
:
1 | const map = new Map(); |
注意:只有对同一个对象的引用,Map结构才将其视为同一个键
1 | const map = new Map(); |
上面的set
和get
看起来是同一个键,但实际上是两个值,内存地址不一样,因此get
无法获取该键,返回undefined
。可以推理,同样的值的两个实例,在Map结构中被视为两个键:
1 | const map = new Map(); |
从上面的例子可以得出,Map的键实际上是和内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题。我们在扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map就将其视为一个键,比如0
和-0
就是一个键,布尔值true
和字符串'true'
是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然NaN
不严格相等于自身,但Map将其视为同一个键(这点和Set结构一致)。
1 | const map = new Map(); |
遍历方法
Map结构提供三个遍历器生成函数和一个遍历方法:
keys()
返回键名遍历器values()
返回键值遍历器entries()
返回所有成员遍历器forEach()
遍历Map的所有成员
特别注意的是:Map的遍历顺序就是插入顺序。
1 | const map = new Map(); |
可以用for…of
来遍历Map:
1 | for (let [key, value] of map) { |
可以发现,Map的默认遍历器接口是entries
方法:
1 | console.log(Map.prototype[Symbol.iterator]); |
Map的forEach
方法与数组、Set的forEach
方法类似,可以实现遍历,也可以接受第二个参数,用来绑定this。例如:
1 | const map = new Map(); |
没啥想总结的,技术整理~🙄