ES6新特性(3)Set集合、Map集合
1 Set集合
Set集合:是一种数据结构,结构类似于数组,且没有重复的值。主要用于数组去重,字符串去重。
1.1 操作方法
方法 | 含义 |
---|---|
add() | 添加值,返回Set结构本身 |
delete() | 删除值,返回一个boolean表示是否删除成功 |
has() | 判断该值是否存在,并返回一个boolean |
clear() | 清除所有值,没有返回值 |
add()使用示例:
let s = new Set();
s.add(1); //添加一个值1
console.log(s); //返回s结构本身
结果为:Set(1) { 1 }
let s = new Set();
s.add(1).add(2).add(1); //连续添加三次数,其中1重复添加
console.log(s); //输出Set结构本身
结果为:Set(2) { 1, 2 }
上面的代码中添加了两次1,但是s中只有一个1,因此Set中没有重复的值。根据观察,前两次的结果前面都加了“Set”,而有时候{ }中才是我们想要的结果,因此我们可以使用拓展运算符(…)将Set的值扩展出来。
let s = new Set();
s.add(1).add(2).add(1); //为s添加3次数字
console.log(...s) //将Set的值扩展出来
输出结果为:1 2
输出结果是一个序列,我们可以通过加一个[ ]来将其变成数组。
let s = new Set();
s.add(1).add(2).add(1); //为s添加3次数字
console.log([...s]) //输出为数组
输出结果为:[ 1, 2 ]
delete()使用示例:
let s = new Set();
s.add(1).add(2); //为Set添加了两个数:1和2
console.log("删除前:", ...s); //输出1 2
s.delete(1); //将1删除掉
console.log("删除后:", ...s); //输出2
输出结果为:
删除前: 1 2
删除后: 2
delete()返回值是判断该数有没有删除成功,因此上一段代码中,s.delete(1)的返回值一定是true。
let s = new Set();
s.add(1).add(2); //为Set添加了两个数:1和2
console.log(s.delete(1)); //输出删除1后的返回值
输出结果为:true
如果删除一个s中不存在的数,那么它的返回值就是false。
let s = new Set();
s.add(1).add(2); //为Set添加了两个数:1和2
console.log(s.delete(3)); //输出删除3后的返回值
console.log(...s); //输出s
结果为:
false
1 2
从上面的结果可以看出,如果删除一个不存在的数,它的返回值是false,但是对原来的Set集合结果并不影响,程序不会报错,会正常输出原来的Set集合。
has()使用示例:
let s = new Set();
s.add(1).add(2); //为s添加两个数1和2
console.log(s.has(1)); //判断s中含不含1
console.log(s.has(3)); //判断s中含不含3
结果为:true false
如果该值存在于Set集合中,那么返回值为true;如果该值不存在于Set集合中,那么返回值为false。
clear()使用示例:
let s = new Set();
s.add(1).add(2); //为s添加两个数1和2
s.clear(); //清除所有的值
console.log(s);
输出结果为:Set(0) {}
1.2 遍历方法
由于Set只有键值没有键名,也可以说键和值是同一个(键、值相同,可以省略) ,所keys和values返回值相同。
let set = new Set();
set.add(1).add(2).add(3); //添加3个数:1,2,3
for (let i of set.keys()) { //遍历set集合的键,keys()代表键
console.log(i); //输出set集合的键
}
输出结果为:1 2 3
let set = new Set();
set.add(1).add(2).add(3); //添加3个数:1,2,3
for (let i of set.values()) { //遍历set集合的键,keys()代表键
console.log(i); //输出set集合的键
}
输出结果为:1 2 3
let set = new Set();
set.add("hello").add("world"); //添加两个字符串
for (let i of set.entries()) { //遍历set的键值对,entries()代表键值对
console.log(i); //输出遍历的结果
}
输出结果为:
[ ‘hello’, ‘hello’ ]
[ ‘world’, ‘world’ ]
使用forEach()遍历Set集合。
let set = new Set();
set.add("hello").add("world"); //为set添加两个字符串
set.forEach((key, val) => { //遍历键和值
console.log(key + "||" + val); //输出键和值
})
输出结果为:
hello||hello
world||world
Set可以接受一个数组作为参数。
let arr = ["小红", "小明", "小强", "小明"];
let set = new Set(arr); //将arr数组作为参数传给set集合
console.log(...set); //输出set集合
结果为:小红 小明 小强
【案例】Set实现并集与交集。
let arr1 = [4, 5, 6];
let arr2 = [5, 6, 7];
let setA = new Set(arr1);
let setB = new Set(arr2);
//求并集,将两个集合合并在一起,去掉多余的重复的数字
let bingji = new Set([...setA, ...setB]);
console.log(...bingji); //输出并集的结果
//求交集,将两个集合合并在一起,求重复的数字,使用filter过滤器得到结果
//在setA中过滤出与setB共同含有的值
let jiaoji = new Set([...setA].filter(val => setB.has(val)));
console.log(...jiaoji); //输出交集的结果
结果为:
4 5 6 7
5 6
1.3 WeakSet
WeakSet只能是对象的集合,而不能是任何类型的任意值。
WeakSet集合中对象的引用为弱引用。如果没有其他的对WeakSet中对象的引用,那么这些对象会被当成垃圾回收掉。
这也意味着WeakSet中没有存储当前对象的列表。正因为这样,WeakSet 是不可枚举的。即WeakSet中对对象的引用不会被考虑进垃圾回收机制,即只要没有其他的对象引用该对象,则该对象就会被回收,而不管它在不在 WeakSet。
WeakSet 支持 add,has 和 delete 方法,但不支持 size 和 keys(),并且不可迭代,无法遍历。
为weakSet添加对象,查看其返回值:
let jack = { name: "jack" }; //jack是一个对象
let weakSet = new WeakSet();
weakSet.add(jack); //为weakSet添加一个对象
console.log(weakSet.has(jack)); //判断weakSet是否含有jack,并输出返回值
得到结果为:true
删除刚刚添加的对象,查看返回值:
let jack = { name: "jack" }; //jack是一个对象
let weakSet = new WeakSet();
weakSet.add(jack); //为weakSet添加一个对象
weakSet.delete(jack); //将jack从weakSet中删除掉
console.log(weakSet.has(jack)); //判断weakSet是否含有jack,并输出返回值
得到结果:false
为WeakSet添加除了对象之外的内容,查看其结果:
let jack = { name: "jack" }; //jack是一个对象
let weakSet = new WeakSet();
weakSet.add(1); //为weakSet添加除对象之外的值
程序会报错:TypeError: Invalid value used in weak set,因为WeakSet中只能存放对象。
WeakSet 的应用场景/好处:用于存储DOM节点,而不用担心这些节点从文档移除时会引发内存泄露。
2 Map集合
2.1 Map概述
JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
为了解决这个问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串——值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。
ES6中的Map类型是一种储存着许多键值对的有序列表,其中的键名和对应的值支持所有的数据类型。键名的等价性判断是通过调用Object.is()
方法实现的,所以数字5与字符串"5"会被判定为两种类型,可以分别作为独立的两个键出现在程序中,这一点与对象不一样,因为对象的属性名总会被强制转换成字符串类型。
注意:
1、有一个例外,Map集合中将+0和-0视为相等,与Object.is()结果不同。
2、如果需要“键值对”的数据结构,Map比Object更合适,具有极快的查找速度
2.2 基本方法和属性
1、属性:size,返回Map中的元素个数
2、基本方法
方法 | 含义 |
---|---|
set() | 给Map添加数据,返回添加后的Map (给已存在的键赋值会覆盖掉之前的值) |
get() | 获取某个key的值,返回key对应的值,没有则返回undefined |
has() | 检测是否存在某个key,返回布尔值 |
delete() | 删除某个key及其对应的value,返回布尔值,成功:true; 失败:false |
clear() | 清除所有的值,返回 undefined |
size、set()使用示例:
let map = new Map();
map.set("name", "橘猫吃不胖"); //为map添加数据
map.set("age", 2); //为map添加数据
console.log(map.size); //输出map中元素个数(长度)
console.log(map); //输出添加数据后的map
输出结果为:
2
Map(2) { ‘name’ => ‘橘猫吃不胖’, ‘age’ => 2 }
get()使用示例:
let map = new Map();
map.set("name", "橘猫吃不胖"); //为map添加数据
map.set("age", 2); //为map添加数据
console.log(map.get("name")); //输出name对应的值
console.log(map.get("address")); //当没有该键时返回undefined
输出结果为:
橘猫吃不胖
undefined
has()使用示例:
let map = new Map();
map.set("name", "橘猫吃不胖"); //为map添加数据
map.set("age", 2); //为map添加数据
console.log(map.has("name")); //判断map中是否含有name
console.log(map.has("address")); //判断map中是否含有address
输出结果为:
true
false
delete()使用示例:
let map = new Map();
map.set("name", "橘猫吃不胖"); //为map添加数据
map.set("age", 2); //为map添加数据
console.log(map.delete("name")); //删除map中的name
console.log(map.delete("address")); //删除map中的address
输出结果为:
true
false
clear()使用示例:
let map = new Map();
map.set("name", "橘猫吃不胖"); //为map添加数据
map.set("age", 2); //为map添加数据
console.log(map); //输出添加值之后的map
console.log(map.clear()); //输出清除所有值之后的返回值
console.log(map); //输出清除值之后的map
输出结果为:
Map(2) { ‘name’ => ‘橘猫吃不胖’, ‘age’ => 2 }
undefined
Map(0) {}
2.3 遍历方法
注意:Map的遍历顺序就是插入顺序
方法 | 含义 |
---|---|
keys() | 获取Map的所有key |
values() | 获取Map的所有值 |
entries() | 获取Map所有成员 |
forEach() | 遍历Map的所有成员 |
//创建一个map集合,传入一个二维数组
const map = new Map([
["F", "no"],
["T", "yes"]
])
console.log(map);
输出结果为:Map(2) { ‘F’ => ‘no’, ‘T’ => ‘yes’ }
keys()使用示例:
//创建一个map集合,传入一个二维数组
const map = new Map([
["F", "no"],
["T", "yes"]
])
for (let key of map.keys()) { //遍历map的键
console.log(key); //输出map的键
}
输出结果为:F T
values()使用示例:
//创建一个map集合,传入一个二维数组
const map = new Map([
["F", "no"],
["T", "yes"]
])
for (let value of map.values()) { //遍历map的值
console.log(value); //输出map的值
}
输出结果为:
no
yes
entries()使用示例:
//创建一个map集合,传入一个二维数组
const map = new Map([
["F", "no"],
["T", "yes"]
])
for (let item of map.entries()) { //遍历map所有成员
console.log(item); //输出map所有成员
}
输出结果为:
[ ‘F’, ‘no’ ]
[ ‘T’, ‘yes’ ]
或者:
//创建一个map集合,传入一个二维数组
const map = new Map([
["F", "no"],
["T", "yes"]
])
for (let [key, value] of map.entries()) { //遍历map成员
console.log(key, value); //输出map成员
}
输出结果为:
F no
T yes
或者:
const map = new Map([
["F", "no"],
["T", "yes"]
])
// 等同于使用map.entries()
for (let [key, value] of map) { //遍历map成员
console.log(key, value); //输出map成员
}
输出结果为:
F no
T yes
2.4 转为数组
Map结构转为数组结构,比较快速的方法是使用扩展运算符(…)。
//创建一个map集合
const map = new Map([
[1, "one"],
[2, "two"],
[3, "three"]
])
//将map的键转化为一个数组
console.log(...map.keys()); //1 2 3
//将map的值转化成一个数组
console.log(...map.values()); //one two three
//将map对象转化成一个数组
console.log(...map.entries()); //[ 1, 'one' ] [ 2, 'two' ] [ 3, 'three' ]
//将map转化成一个数组
console.log(...map); //[ 1, 'one' ] [ 2, 'two' ] [ 3, 'three' ]
2.5 Map的遍历和过滤
结合数组的map方法、filter方法,可以实现Map的遍历和过滤。
const map0 = new Map();
map0.set(1, "a"); //为map0添加数据
map0.set(2, "b"); //为map0添加数据
map0.set(3, "c"); //为map0添加数据
//filter()的使用
const map1 = new Map(
//将map0中键小于3的过滤出来
[...map0].filter(([k, v]) => k < 3)
);
console.log(map1); //Map(2) { 1 => 'a', 2 => 'b' }
//map()的使用
const map2 = new Map(
//将map0中的成员,键变成2倍,值的前面加"_"
[...map0].map(([k, v]) => [k * 2, "_" + v])
)
console.log(map2); //Map(3) { 2 => '_a', 4 => '_b', 6 => '_c' }
2.6 forEach()
const map = new Map([[1, 'one'], [2, 'two'], [3, 'three']]);
map.forEach((value, key, map) => {
console.log(value, key, map);
})
输出结果为:
one 1 Map(3) { 1 => ‘one’, 2 => ‘two’, 3 => ‘three’ }
two 2 Map(3) { 1 => ‘one’, 2 => ‘two’, 3 => ‘three’ }
three 3 Map(3) { 1 => ‘one’, 2 => ‘two’, 3 => ‘three’ }
2.7 WeakMap
WeakMap是弱引用Map集合,也用于存储对象的弱引用。WeakMap集合中的键名必须是一个对象,如果使用非对象键名会报错。
集合中保存的是这些对象的弱引用,如果在弱引用之外不存在其他的强引用,引擎的垃圾回收机制会自动回收这个对象,同时也会移除WeakMap集合中的键值对。但是只有集合的键名遵从这个规则,键名对应的值如果是一个对象,则保存的是对象的强引用,不会触发垃圾回收机制。
1、使用WeakMap集合
ES6中的WeakMap类型是一种存储着许多键值对的无序列表,列表的键名必须是非null类型的对象,键名对应的值则可以是任意类型。
WeakMap的接口与Map非常相似,通过set()方法添加数据,通过get()方法获取数据。
let map = new WeakMap();
const o1 = {}; //o1是一个空对象
const o2 = function () { }; //o2是一个空函数
map.set(o1, o2); //给map添加数据,键和值可以是任意对象,甚至是另一个WeakMap对象
map.set(o2, "橘猫吃不胖"); //为map添加数据
console.log(map.get(o1)); //[Function: o2]
console.log(map.get(o2)); //橘猫吃不胖
WeakMap集合不支持size属性,从而无法验证集合是否为空。
2、WeakMap集合支持的方法
方法 | 含义 |
---|---|
has() | 检测给定的键在集合中是否存在 |
delete() | 移除指定的键值对 |
has()使用示例:
let map = new WeakMap();
const o1 = {}; //o1是一个空对象
const o2 = function () { }; //o2是一个空函数
map.set(o1, o2); //给map添加数据,键和值可以是任意对象,甚至是另一个WeakMap对象
map.set(o2, "橘猫吃不胖"); //为map添加数据
console.log(map.has(o1)); //判断map中是否含有o1,true
console.log(map.has(o3)); //判断map中是否含有o3,程序报错,ReferenceError: o3 is not defined
delete()使用示例:
let map = new WeakMap();
const o1 = {}; //o1是一个空对象
const o2 = function () { }; //o2是一个空函数
map.set(o1, o2); //给map添加数据,键和值可以是任意对象,甚至是另一个WeakMap对象
map.set(o2, "橘猫吃不胖"); //为map添加数据
console.log(map.delete(o1)); //删除map中的o1,删除成功返回true
console.log(map.has(o1)); //判断map中是否含有o1
输出结果为:true false
3、WeakMap集合的用途
- 储存DOM元素
<button id="btn">点击</button>
<script>
//获取button元素
let myElement = document.getElementById("btn");
let myWeakmap = new WeakMap();
//为myWeakmap添加数据
myWeakmap.set(myElement, { timesClicked: 0 });
//为按钮添加click事件
myElement.addEventListener("click", function () {
//获取myWeakmap键为myElement的值
let logoData = myWeakmap.get(myElement);
//点击一次加一次1
logoData.timesClicked++;
//输出点击的次数
console.log(logoData.timesClicked);
})
</script>
代码中,myElement是一个DOM节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在WeakMap里,对应的键名就是myElement。一旦这个DOM节点删除,该状态就会自动消失,不存在内存泄漏风险
- 注册监听事件的listener对象,就很合适用WeakMap实现。
<button class="element1">按钮1</button>
<button class="element2">按钮2</button>
<script>
//获取元素
let element1 = document.querySelector(".element1");
let element2 = document.querySelector(".element2");
const listener = new WeakMap();
//为listener添加两个数据
listener.set(element1, "handler1");
listener.set(element2, "handler2");
//为element1添加click事件
element1.addEventListener("click", function () {
console.log(listener.get(element1));
})
//为element2添加click事件
element2.addEventListener("click", function () {
console.log(listener.get(element2));
})
</script>
监听函数放在WeakMap里面。一旦 DOM 对象消失,跟它绑定的监听函数也会自动消失。
- 部署私有属性
我们创建对象的时候,通常写一个构造函数。
function Person(name) {
this._name = name;
}
Person.prototype.getName = function () {
return this._name;
}
但这时,创建一个Person 对象的时候,我们可以直接访问name属性:
const person = new Person("橘猫吃不胖");
console.log(person._name); //橘猫吃不胖
我们不想让用户直接访问name属性, 直接使用下面的方法将name包装成私有属性。
let Person = (function () {
let privateData = new WeakMap();
function Person(name) { //定义一个函数,参数为name
privateData.set(this, { name: name }); //为privateData设置数据
}
Person.prototype.getName = function () { //设置getName函数
return privateData.get(this).name; //获得privateData中name键的值的name
}
return Person;
}()); //()()该函数设置之后就调用
const person = new Person("橘猫吃不胖");
console.log(person.getName());