ES6精进之路之Set、Map

一、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的键名可以是对象,所以k1k2不会被转换为字符串,他们是相互独立的元素;
3.如果调用get方法时传入的键名在set中不存在,则会返回undefined;
4.set集合中不存在重复的值,可以用来做数组去重;
5.Set+0-0被认为是相等的;
6.NaNNaN是不恒等的,但是在 Set 中认为NaNNaN相等,所有只能存在一个;
7.undefinedundefined 是恒等的, 所以不能重复;
8.setforEach方法的回调函数中,键和值相等;
9.第三个参数ownerSetset相等;
10.forEach函数的第二个参数,作为回调函数中this的引用;
11.set中不能直接通过索引访问集合中的元素,需要先转换为数组;
12.set的遍历顺序就是插入顺序;
13.set结构的实例默认可遍历,默认遍历器生成函数就是它的values方法,所以可以省略掉values方法;
14.数组独有方法(如mapfilterreduce等)需要先将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() 方法实现。
复制代码

小结:

  1. Map成本低,优先使用Map;
  2. 如果要求数据的唯一性,考虑使用set;
  3. 以后在开发中要尽量多的使用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.相比于数组、对象,SetMap在性能上更好,代码执行速度更快,且代码简洁无冗余,易于维护,所以在项目开发中优先使用。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享