Set
基本用法
ES6 提供了新的数据结构 Set
。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set
本身是一个构造函数,用来生成 Set 数据结构。
const setArr = new Set();[1,2,3,4,2,2,3,4].forEach(x => setArr.add(x));for (let i of setArr) {console.log(i);
}
// 1,2,3,4
复制代码
Set
函数可以接受一个数组(或者具有 iterable
接口的其他数据结构)作为参数,用来初始化。 特点:
- 以数组为参数, 可以去重
- 向
Set
加入值的时候,不会发生类型转换,所以4和"4"是两个不同的值。
// 以数组为参数
const set = new Set([1, 2, 3, 4, 4, '4'])
[...set] // [1, 2, 3, 4, '4']
set.size // 5// 一个类似数组的带 iterable 接口的对象
const set = new Set(document.querySelectorAll('div'))复制代码
将Set
结构转换成数组有两个简单的方法
[...set] // [1, 2, 3, 4, '4']Array.from(set) // [1, 2, 3, 4, '4']
复制代码
Set 实例的属性和方法
Set
结构的实例有以下属性。
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
Set
实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。
四个操作方法。
add(value)
:添加某个值,返回Set
结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值。
const s = new Set()
s.add(1).add(2).add(2)s.size // 2s.has(1) // true
s.has(2) // true
s.has(3) // falses.delete(2)
s.size // 1
s.has(2) // false复制代码
四个遍历方法
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员
keys
方法、values
方法、entries
方法返回的都是遍历器对象。由于 Set
结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys
方法和values
方法的行为完全一致。
const set = new Set(['a', 'b', 'c']);for (let item of set.keys()) {console.log(item);
}
// a
// b
// cfor (let item of set.values()) {console.log(item);
}
// a
// b
// cfor (let item of set.entries()) {console.log(item);
}
// ["a", "a"]
// ["b", "b"]
// ["c", "c"]复制代码
对象结构 在使用这个几个方法的时候类似,但有一定区别
const obj = { 1: 'a', 2: 'b', 3: 'c' }
for (let item of Object.keys(obj)) {console.log(item);
}
// 1
// 2
// 3for (let item of Object.values(obj)) {console.log(item);
}
// a
// b
// cfor (let item of Object.entries(obj)) {console.log(item);
}
// [1, "a"]
// [2, "b"]
// [3, "c"]
复制代码
Set
结构的实例默认可遍历,它的默认遍历器生成函数就是它的values
方法。
这意味着,可以省略values
方法,直接用for...of
循环遍历 Set
。
for (let i of set) {console.log(i)
}
// a
// b
// c复制代码
forEach()
: Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
不过key
和value
是同一个
set.forEach((value, key) => console.log(key + ' : ' + value))
// a: a
// b: b
// c: c
复制代码
遍历的应用
- 去重
- 实现并集(Union)、交集(Intersect)和差集(Difference)
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}// 差集
let difference = new Set([...union].filter(x => !intersect.has(x)));
// Set {1}
复制代码
如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
复制代码
WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
(1):WeakSet 可以接受数组和类似数组的对象作为参数。该数组的所有成员都会自动成为WeakSet的实例对象的成员。数组成员只能是对象,不能是其他类型的值。否则报错。
const a = [[1, 2], [3, 4], {a: 1}]
const ws = new WeakSet(a)
// WeakSet {[1, 2], [3, 4]}const b = [1, 2, [1,2]]
new WeakSet(b) // Uncaught TypeError: Invalid value used in weak set
复制代码
(2):WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
Map
含义和基本用法
一个常规的对象本质应该是键值对的合集(即Hash结构)。它的键应该是一个字符串。 但是有时候需要使用其他类型比如对象来做 键值对的 键。 于是就有了Map
结构
const data = {};
const element = document.getElementsByTagName('div')data[element] = 'div';
data[element] // "div"
data['[object HTMLCollection]'] // "div"// {[object HTMLCollection]: "div"}const elementSpan = document.getElementsByTagName('span')
data[elementSpan] // "div"复制代码
element
被转化成了'[object HTMLCollection]'
只是个字符串。 并不能达到通过element
取到值的效果。但是,使用Map
结构可以。 Map
结构与对象很相似也是键值对, 但Map
的键可以不是字符串,可以是各种类型的值(包括对象)。如果你需要“键值对”的数据结构,Map
比 Object
更合适。
const dataMap = new Map()
dataMap.set(element, 'div')
dataMap.get(element) // div// has,delete
dataMap.has(element) // true
dataMap.delete(element) // true
dataMap.has(element) // false复制代码
以上 new Map()
实例对象为一个Map
结构,提供了set
、get
、has
、delete
几个方法。轻松了实现了增删改查。 构造函数Map
可以接受一个数组作为参数。
const map = new Map([ ['key1', 'value1'], ['key2', 'value2'] ])复制代码
任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。如:数组,Set
结构, Map
结构。
const set = new Set([['a', 1],['b', 2]]);
const m1 = new Map(set);
m1.get('a') // 1const m2 = new Map([['c', 3]]);
const m3 = new Map(m2);
m3.get('c') // 3
复制代码
注意:只有对同一个对象的引用,Map 结构才将其视为同一个键。
const k1 = {a: 1 }
const k2 = {a: 1 }
const map = new Map()map.set(k1, 111)
map.get(k2) // undefinedmap.set(k2, 222)
map.get(k1) // 111
map.get(k2) // 222
复制代码
因为即便值相同,但是两个对象内存地址是不一样的。
这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键。
实例的属性和操作方法
- set(key, value):
set
方法设置键名key对应的键值为value
,然后返回整个Map
结构。如果key
已经有值,则键值会被更新,否则就新生成该键。 - get(key):
get
方法读取key
对应的键值,如果找不到key
,返回undefined
。 - size 属性: 返回
Map
结构的成员总数。 - has(key):
has
方法返回一个布尔值,表示某个键是否在当前Map
对象之中。 - delete(key):
delete
方法删除某个键,返回true
。如果删除失败,返回false
。 - clear():
clear
方法清除所有成员,没有返回值。
const map = new Map();
// 可以采用链式写法。
map.set('a', 1).set('b', 2)map.size // 2map.get('b') // 2
map.set('b', 222)
map.get('b') // 222
map.get('c') // undefinedmap.has('b') // truemap.delete('b') // true
map.has('b') // falsemap.clear()
map.size // 0
复制代码
遍历方法
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员
需要特别注意的是,Map 的遍历顺序就是插入顺序。
const map = new Map([['a', 1], ['b', 2]]);for (let key of map.keys()) {console.log(key);
}
// "a"
// "b"for (let value of map.values()) {console.log(value);
}
// 1
// 2for (let item of map.entries()) {console.log(item);
}
// ["a", 1]
// ["b", 2]// 或者
for (let [key, value] of map.entries()) {console.log(key, value);
}
// "a" 1
// "b" 2// 等同于使用map.entries()
for (let [key, value] of map) {console.log(key, value);
}
// "a" 1
// "b" 2
复制代码
与其他数据结构的互相转换
(1)Map 与数组的互换
const arr1 = [[{'k1': 11}, 11 ],['a', 1]]
const map = new Map(arr1)[...map] // [[{'k1': 11}, 11 ],['a', 1]]
Array.from(map) // [[{'k1': 11}, 11 ],['a', 1]]
复制代码
(2) Map 与对象的互换
如果所有 Map 的键都是字符串,它可以转为对象。
const obj1 = { a:1, b:2 }
const obj2 = {}
const map = new Map()for(let key of Object.keys(obj1)) {map.set(key, obj1[key])
}console.log(map) // Map(2) {"a" => 1, "b" => 2}for (let [key,value] of map) {obj2[key] = value
}console.log(obj2) // {a: 1, b: 2}
复制代码
(3) JSON 要转换成 Map 可以先转换成数组或者对象,然后再转换。
WeakMap
WeakMap
结构与Map
结构类似,也是用于生成键值对的集合。
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"
复制代码
WeakMap与Map的区别有两点。
- WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
- WeakMap的键名所指向的对象,不计入垃圾回收机制。
const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!复制代码
WeakMap 的语法:
WeakMap
与 Map
相似但有两个区别:
- 没有遍历操作(即没有key()、values()和entries()方法)。
- 无法清空,即不支持clear方法。 因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。
WeakMap 的用途
WeakMap 应用的典型场合就是 DOM 节点作为键名。 WeakMap 的另一个用处是部署私有属性。
const _counter = new WeakMap();
const _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)();}}
}const c = new Countdown(2, () => console.log('DONE'));c.dec()
c.dec()复制代码
习题: 一· 求set 的值
let arr1 = [1, 2, 3, '3', 2]
const set = new Set(arr1)
复制代码
二. 求遍历的输出
for (let [key, value] of set.entries()) {console.log(key === value)
}
复制代码
三. set 转换数成数组
四. 求 data[obj1],
const obj1 = { a: 1}
const obj2 = { b: 2}const data = {}
data[obj1] = 11
data[obj2] = 22// 求 data[obj1]复制代码
五. 1.求 map.get({a: 1}), 2.如果 map 要转换成其他结果,应该是对象还是数组
const map = new Map()
map.set({a: 1}, 111)map.get({a: 1})复制代码