本文详细介绍ES6新增的Set和Map数据结构。Set用于存储唯一值,支持添加、删除及遍历操作;Map提供键值对存储,键类型不限于字符串。此外还涵盖WeakSet弱引用特性及两者底层实现与区别,帮助开发者高效使用这些容器。
本文通过具体实例解析ES6中Set和Map的使用方法,供读者参考学习。
Set
ES6引入了Set这一新数据结构。它类似于数组,但其核心特征在于成员值具有唯一性,不允许存在重复值。
Set构造函数接收一个数组(或类数组对象)作为参数,用于初始化Set实例。
// 例一
var set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
var s = new Set();
[2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
注意:在Set内部,两个NaN被视为相等。对于两个不同的对象,它们总是不相等的。可以通过length属性来检测Set的大小。
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
var arr=[
{id:1,name:'叶落森'},
{id:2,name:'叶落森'},
{id:3,name:'叶落森'},
{id:4,name:'叶落森'},
{id:2,name:'叶落森'}
]
const s = new Set();
arr.forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
Set提供了四种主要操作方法:
add(value):用于添加某个值,返回Set结构本身。delete(value):用于删除某个值,返回一个布尔值,指示删除操作是否成功。has(value):返回一个布尔值,判断该值是否为Set的成员。clear():用于清除所有成员,该方法没有返回值。
let s = new Set();
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
console.log(s.delete(2));//true
s.has(2) // false
Set内部的元素支持使用for...of...进行遍历。
遍历操作
Set结构的实例提供了四个遍历方法,可用于遍历其成员。
keys():返回键名的遍历器。values():返回键值的遍历器。entries():返回键值对的遍历器。forEach():使用回调函数遍历每个成员。
需要特别指出的是,Set的遍历顺序与插入顺序一致。这一特性在某些场景下非常有用,例如使用Set保存回调函数列表时,调用时可保证按照添加顺序执行。
(1)keys(),values(),entries()
keys方法、values方法、entries方法返回的都是遍历器对象。由于Set结构没有独立的键名,只有键值(即键名和键值是同一个值),因此keys方法和values方法的行为完全一致。
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
Set结构的实例默认可遍历,其默认遍历器生成函数即为它的values方法。
这意味着,可以省略values方法,直接使用for...of循环来遍历Set。
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red
// green
// blue
Set结构的实例与数组类似,也拥有forEach方法,用于对每个成员执行某种操作,该方法没有返回值。
set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
Map
Map结构提供了“值—值”的对应关系,是一种更完善的Hash结构实现。如果需要“键值对”的数据结构,Map比Object更为合适。它类似于对象,也是键值对的集合,但“键”的范围不限于字符串,各种类型的值(包括对象)都可以作为键。
var m = new Map();
var o = {p: "Hello World"};
m.set(o, "content")
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
注意,只有对同一个对象的引用,Map结构才将其视为同一个键。这一点需要非常小心。
var map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
上述代码中的set和get方法,表面是针对同一个键,但实际上这是两个不同的值,内存地址不同,因此get方法无法读取该键,返回undefined。
注:如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map将其视为一个键,这包括0和-0。另外,虽然NaN不严格等于自身,但Map将其视为同一个键。
实例属性和方法包括:size、set、get、has、delete、clear。
遍历方法包括:keys()、values()、entries()、forEach()。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
将Map结构转为数组结构,较为快速的方法是使用扩展运算符(...)。
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
结合数组的map方法、filter方法,可以实现Map的遍历和过滤(Map本身没有map和filter方法)。
const map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
此外,Map还有一个forEach方法,与数组的forEach方法类似,也可实现遍历。
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
区别:
set是一种关联式容器,其特性如下:
- set以RBTree作为底层容器。
- 所得元素只有key没有value,value就是key。
- 不允许出现键值重复。
- 所有元素都会被自动排序。
- 不能通过迭代器来改变set的值,因为set的值就是键。
map和set一样是关联式容器,它们的底层容器都是红黑树,区别在于map的值不作为键,键和值是分开的。其特性如下:
- map以RBTree作为底层容器。
- 所有元素都是键+值存在。
- 不允许键重复。
- 所有元素是通过键进行自动排序的。
- map的键是不能修改的,但是其键对应的值是可以修改的。
weakset
WeakSet结构与Set类似,也是不重复的值的集合。
WeakSet和Set的区别:
- WeakSet的成员只能是对象,而不能是其他类型的值。
- WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用。也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。
WeakMap可以参考WeakSet。
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容可查看本站专题:《JavaScript操作DOM技巧总结》、《JavaScript页面元素操作技巧总结》、《JavaScript事件相关操作与技巧大全》、《JavaScript查找算法技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript错误与调试技巧总结》。
希望本文所述对大家JavaScript程序设计有所帮助。