一、Set
set是一种有序列表,类似数组,但是成员的值都是唯一的,没有重复。通过set集合可以快速访问其中的数据,更有效的追踪各种离散值。
1. 常用方法
// @add: 添加元素
// @delete: 移除元素
// @clear: 清除set集合中所有元素
// @has: 判断集合中是否存在某值
// size属性:获取集合目前的元素数量
let set = new Set();
let k1 = {};
let k2 = {};
set.add(7);
set.add('7');
set.add(k1);
set.add(k2);
set.add(7); // 重复 - 本次调用会被忽略
console.log(set.size); // 4
console.log(set.has(7)); // true
console.log(set.get(8)); // undefined
set.delete(7);
console.log(set.has(7)); // false
console.log(set.size); // 3
set.clear();
console.log(set.has('7')): // false
console.log(set.size); // 0
set.add(+0).add(-0);
console.log(set.size); // 1
set.clear();
set.add(NaN).add(NaN);
console.log(set.size); // 1
set.clear();
set.add(undefined).add(undefined);
console.log(set.size); // 1
复制代码
2. 初始化
let set = new Set([1,2,3,4,5,5,5,5]);
console.log(set.size); // 5
复制代码
3. 遍历方法
// @keys(): 返回键名的遍历器
// @values(): 返回键值的遍历器
// @entries(): 返回键值对的遍历器
// @forEach(): 使用回调函数遍历每个成员
// 由于set方法的键名和键值保持一致,所以keys方法和values方法行为一致。
let 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']
for (let item of set) {
console.log(item);
}
// a
// b
// c
let set = new Set([1, 2, 3]);
set.forEach((value, key, ownerSet) => {
console.log(key + '' + value);
console.log(ownerSet === set)
})
// 1 1
// true
// 2 2
// true
// 3 3
// true
let set = new Set([1, 2, 3]);
let processor = {
output(val) {
console.log(val);
},
process(dataSet) {
dataSet.forEach(function(val) {
this.output(val);
}, this);
}
}
processor.process(set);
// 1
// 2
// 3
复制代码
4. 应用
// 使用Set很容易实现并集(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}
// 差集 a - b
let difference = new Set([...a].filter(x => !b.has(x)));
// Set { 1 }
// 差集 b - a
let difference = new Set([...b].filter(x => !a.has(x)));
// Set { 4 }
复制代码
5. 小结:
1.set
集合内部不会对所存值进行强制类型转换,数字7
和字符串'7'
是两个独立元素;
2.set
的键名可以是对象,所以k1
和k2
不会被转换为字符串,他们是相互独立的元素;
3.如果调用get
方法时传入的键名在set
中不存在,则会返回undefined
;
4.set
集合中不存在重复的值,可以用来做数组去重;
5.Set
中+0
和-0
被认为是相等的;
6.NaN
与NaN
是不恒等的,但是在 Set
中认为NaN
与NaN
相等,所有只能存在一个;
7.undefined
与undefined
是恒等的, 所以不能重复;
8.set
的forEach
方法的回调函数中,键和值相等;
9.第三个参数ownerSet
与set
相等;
10.forEach
函数的第二个参数,作为回调函数中this
的引用;
11.set
中不能直接通过索引访问集合中的元素,需要先转换为数组;
12.set
的遍历顺序就是插入顺序;
13.set
结构的实例默认可遍历,默认遍历器生成函数就是它的values
方法,所以可以省略掉values
方法;
14.数组独有方法(如map
、filter
、reduce
等)需要先将Map
转换成数组再使用。
二、Map
由于对象中的属性名只能是字符串,所以es6拓展了Map类型。Map类型是一种储存着许多键值对的有序列表,其中键名和对应的值支持所有的数据类型。
1. 常用方法:
// @set: 添加键值对
// @get: 获取传入的属性所对应的属性值
// @delete: 移除键值对
// @clear: 移除Map集合中的所有键值对
// @has: 检测指定的键名在Map集合中是否存在
// size属性: 求Map集合中键值对的数量
let map = new Map();
let k1 = {}, key2 = {};
map.set(k1, 5);
map.set(k2, 40);
map.set('name', 'xiaoming');
map.set('age', 25);
console.log(map.size); // 4
console.log(map.has('name')); // true
console.log(map.get('name')); // xiaoming
map.set('name', 'xiaohong');
console.log(map.has('name')); // true
console.log(map.get('name')); // xiaohong
console.log(map.delete(k1));
console.log(map.has(k1)); // false
console.log(map.get(k1)); // undefined
console.log(map.size); // 3
map.clear();
console.log(map.has(name)); // false
console.log(map.get(name)); // undefined
console.log(map.size); // 0
复制代码
2. 初始化
let map = new Map([['name', 'xiaoming'], ['age', 25]]);
console.log(map.has('name')); // true
console.log(map.get('name')); // xiaoming
console.log(map.has('age')); // true
console.log(map.get('age')); // 25
复制代码
3. 遍历方法
// @keys(): 返回键名的遍历器
// @values(): 返回键值的遍历器
// @entries(): 返回所有成员的遍历器
// @forEach(): 遍历Map的所有成员
let map = new Map([['name', 'xiaoming'], ['age', 25]]);
for (let key of map.keys()) {
console.log(key);
}
// 'name'
// 'age'
for (let value of map.values()) {
console.log(value);
}
// 'xiaoming'
// 25
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// 'name' 'xiaoming'
// 'age' 25
for (let [key, value] of map) { // 等同于使用map.entries()
console.log(key, value);
}
// 'name' 'xiaoming'
// 'age' 25
map.forEach(function(value, key, ownerMap) {
console.log(key + ' ' + value);
console.log(ownerMap === map);
});
// name xiaoming
// true
// age 25
// true
复制代码
小结:
1.map
中的属性名可以是任意类型,不会强制转换为字符串(对象会);
2.map
中的size
属性与set
中类似,其值为集合中键值对的数量;
3.可以向Map
构造函数传入一个二维数组来初始化Map
集合,子数组中包含一个键值对的键名和值两个元素;
4.如果向Map
中添加相同键名,则原键名会被后者覆盖,相当于修改;
5.从以上示例可以看出,Map
结构的默认遍历器接口(Symbol.iterator
属性)就是entries
方法;
6.Map
中的forEach()
方法与Set
集合和数组中的forEach()
方法类似;
7.遍历的过程中,会按照键值对插入Map
集合的顺序将相应的信息传入forEach()
方法的回调函数,而在数组中,会按照数值型索引值的顺序依次传入回调函数;
8.也可以指定forEach()
函数的第二个参数作为回调函数的this
值。
三、Set、Map、数组、对象的比较
let set = new Set();
let map = new Map();
let array = [];
let obj = {};
// 增
set.add({age: 25});
map.set('age', 25);
array.push({age: 25});
obj['age'] = 25; // 或 obj.age = 25
// 删
set.forEach(item => item.age ? set.delete(item) : '');
map.delete('age');
let index = array.findIndex(item => item.age);
array.splice(index, 1);
delete obj['age']; 或 delete obj.age
delete obj; // 删除整个对象
// 改
set.forEach(item => item.age ? item.age = 26 : '');
map.set('age', 26);
array.forEach(item => item.age ? item.age = 26 : '');
// 或
array.map(item => item.age ? item.age = 26 : ''); // array被修改
obj['age'] = 26; // 或 obj.age = 26
// 查
set.has({age: 25});
map.has('age');
array.find(item => item.age);
'age' in obj
复制代码
// Map和Object的区别
// 1. 一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值。
// 2. Map中的键值是有序的,而对象中的键不是。
// 3. Map的键值对个数可以从 size 属性直接获取,而Object需要手动计算。
// 4. Object有自己的原型,自己对象上的键名有可能会与原型链上的键名重复。
// 5. Map可以直接进行迭代,而Object不行。
复制代码
// Set和Array的区别
// 1. 唯一性:Set中的元素唯一,Array中的元素可以重复。
// 2. 长度: Set通过size属性获取集合长度, Array是通过length属性获取。
// 3. 获取元素:Array可以直接通过索引的方式取值,set只能通过遍历取值。
// 4. 添加元素:Array通过push()方法或者索引的方式添加值,Set通过add()方法添加。且添加后返回Set对象本身,可以进行链式调用。
// 5. 删除元素:Array通过splice方法删除元素,而Set通过delete()方法删除.
// 6. 清空元素:Array通过 array.length = 0 清空,而Set通过clear()方法实现.
// 7. 查询:Array通过 indexOf() 或者 includes() 来查询某值是否存在,而Set通过 has() 方法实现。
复制代码
小结:
- Map成本低,优先使用Map;
- 如果要求数据的唯一性,考虑使用set;
- 以后在开发中要尽量多的使用Set和Map,非特殊情况不使用数组和Object;
四、Set、Map、数组、对象相互转换
// Set转为数组
let set = new Set(['a', 'b', 'c']);
let arr = [...set]; // 或 let arr = Array.from(set)
// ['a', 'b', 'c']
// 数组转Set: 将数组传入Set构造函数即可
let arr = [1, 2, 3, 4, 5];
let set = new Set(arr);
// 返回Set结构:{1, 2, 3, 4, 5}
// Map转为数组
const map = new Map();
map.set(true, 7).set({foo: 3}, ['abc']);
[...map] // [[true, 7], [{foo: 3}, ['abc']]]
// 数组转为Map
new Map([true, 7], [{foo: 3}, ['abc']]);
// Map { true => 7, Object { foo: 3} => ['abc']}
// Map转为对象(Map的所有键都为字符串的情况才可以转换)
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k, v] of strMap) {
obj[k] = v
}
return obj;
}
const map = new Map().set('name', 'xiaoming').set('age', 25);
strMapToObj(map); // { name: 'xiaoming', age: 25 }
// 对象转为Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({ name: 'xiaoming', age: 25 });
// Map {'name' => 'xiaoming', 'age' => 25}
复制代码
小结:
1.Set、Map转为数组都可以直接用 …语法;
2.Map必须是所有键都是字符串时才可转换为对象。
五、总结
1.Set
集合是一种包含多个非重复值的有序列表,值与值之间的等价性是通过Object.is()
方法来判断的,如果相同,则会自动过滤重复的值,因此可以用Set
集合来过滤数组中重复的值。Set
集合不是数组的子类,不能随机访问集合中的值,只能通过has()
方法检测指定的值是否存在,或者通过size
属性查看Set
集合中值的数量。
2.Map
是多个键值对组成的有序集合,键名支持任意数据类型,Map
集合也是通过Object.is()
方法来过滤重复的值。不会进行类型转换,数字7
和字符串'7'
可以分别作为两个独立的键名使用。通过set()
方法可以将任意类型的值添加到集合中,通过get()
方法可以检索集合中的所有值, 通过size
属性可以检查集合中值的数量。
3.相比于数组、对象,Set
、Map
在性能上更好,代码执行速度更快,且代码简洁无冗余,易于维护,所以在项目开发中优先使用。