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 // 2
- s.has(1) // true
- s.has(2) // true
- s.has(3) // false
- s.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
- // c
- for (let item of set.values()) {
- console.log(item);
- }
- // a
- // b
- // c
- for (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
- // 3
- for (let item of Object.values(obj)) {
- console.log(item);
- }
- // a
- // b
- // c
- for (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') // 1
- const 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) // undefined
- map.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 // 2
- map.get('b') // 2
- map.set('b', 222)
- map.get('b') // 222
- map.get('c') // undefined
- map.has('b') // true
- map.delete('b') // true
- map.has('b') // false
- map.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
- // 2
- for (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})
来源: https://juejin.im/post/5ae5de126fb9a07abd0d6e2b