set、map、object 和 array的区别与联系

207 阅读5分钟

《用得上的前端知识》系列 - 你我都很忙,能用100字说清楚,绝不写万字长文

Set

基本用法

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set的遍历顺序就是插入顺序。
Set本身是一个构造函数,用来生成 Set 数据结构。

// 例一 
const set = new Set([1, 2, 3, 4, 4]); 
[...set] // [1, 2, 3, 4] 

// 例二 
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); 
items.size // 5 

// 例三 
const set = new Set(document.querySelectorAll('div')); 
set.size // 56 

// 例四 
const set = new Set(); 
document.querySelectorAll('div').forEach(div => set.add(div)); 
set.size // 56 

属性和方法

常用属性

  • Set.prototype.size:返回Set实例的成员总数。

常用方法

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身;
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功;
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员;
  • Set.prototype.clear():清除所有成员,没有返回值。

遍历

Set 结构的实例有四个遍历方法,可以用于遍历成员。Set的遍历顺序就是插入顺序

  • Set.prototype.keys():返回键名的遍历器;
  • Set.prototype.values():返回键值的遍历器;
  • Set.prototype.entries():返回键值对的遍历器;
  • Set.prototype.forEach():使用回调函数遍历每个成员,该方法还可以有第二个参数,表示绑定处理函数内部的this对象。
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"] 

let set = new Set([1, 4, 9]); 
set.forEach((value, key) => console.log(key + ' : ' + value)) 
// 1 : 1 
// 4 : 4 
// 9 : 9 

补充:
扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构。

let set = new Set(['red', 'green', 'blue']); 
let arr = [...set]; // ['red', 'green', 'blue'] 

和数组的区别

最大且有意义的点在于Set结构“自带”去重功能。

应用场景

实现数组或字符串的去重

let set1 = new Set([1,2,2,5,2]); 
let set2 = new Set('ababbc'); 
console.log( [...set1] );  //[1,2,5]  
console.log( [...set2].join('') ); // "abc" 

使用 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} 

// 差集 
let difference = new Set([...a].filter(x => !b.has(x)));  
// Set {1} 

注意事项

向 Set 加入值的时候,不会发生类型转换,Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,即:

  • NaN === NaN;
  • -0 === +0;
  • 其它情况按照精确相等运算符(===)进行比较。

注,“Same-value-zero equality”算法和精确相等运算符(===)的区别在于:

  • 在“Same-value-zero equality”算法中,NaN等于自身;
  • 而精确相等运算符认为NaN不等于自身。
let set = new Set(); 
let a = NaN; 
let b = NaN; 

set.add(a); 
set.add(b); 
console.log(set); // Set {NaN} 
console.log(set.size); // 1 set.add(null); 

set.add(null); 
console.log(set); // Set {NaN,null} 
console.log(set.size); // 2 

Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。

Set.prototype[Symbol.iterator] === Set.prototype.values // true 

这意味着,可以省略values方法,直接用for…of循环遍历 Set。

let set = new Set(['red', 'green', 'blue']); 
for (let x of set) {   
  console.log(x); 
} 
// red 
// green 
// blue 

Map

基本用法

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 的遍历顺序就是插入顺序。

const m = new Map(); 
const o = {p: 'Hello World'}; 
m.set(o, 'content');
m.get(o); // "content" 

属性和方法

常用属性

size属性返回 Map 结构的成员总数。

常用方法

  • Map.prototype.set(key, value),set方法设置键名key对应的键值为value,然后返回整个 Map 结构。因此Map结构可以使用链式写法。
  • Map.prototype.get(key),get方法读取key对应的键值,如果找不到key,返回undefined。
  • Map.prototype.has(key),has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
  • Map.prototype.delete(key),delete方法删除某个键,返回true。如果删除失败,返回false。
  • Map.prototype.clear(),clear方法清除所有成员,没有返回值。

遍历

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历Map的所有成员,该方法还可以有第二个参数,表示绑定处理函数内部的this对象。
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是一个纯哈希结构,而Object不是(它拥有自己的内部逻辑)。使用delete对Object的属性进行删除操作存在很多性能问题。所以,针对于存在大量增删操作的场景,使用Map更合适。
  • Map在存储大量数据的场景下表现更好,尤其是在key为未知状态,并且所有key和所有value分别为相同类型的情况下。

和对象的区别

  • 意外的键:Object 只能使用字符串或 symbol 做“键”,而 Map 结构的“键”则不受类型限制;
  • 键的类型:Object 原型链上的键名可能和我们在对象上设置的键名产生冲突,而 Map 默认不包含任何键;
  • 键的顺序:Object 的键是无序的,而 Map 的键是有序的(插入顺序);
  • 性能:在频繁增删键值对的场景下,Object 性能较差,Map 的性能更好。

注意事项

向 Map 加入值的时候,内部判断规则和 Set 一致,即:

  • NaN === NaN;
  • -0 === +0;
  • 其它情况按照精确相等运算符(===)进行比较。
let map = new Map();

map.set(-0, 123);
map.get(+0) // 123

map.set(true, 1);
map.set('true', 2);
map.get(true) // 1

map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3

map.set(NaN, 123);
map.get(NaN) // 123

Map的遍历顺序就是插入顺序。

Set、Map的相同与不同

相同:

  • 适合用于数据量大、增删操作频繁的场景;
  • 都是数据集合;
  • 数据是有序的。

不同:

  • Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构;
    • Set 以 [value, value] 的形式保存;
    • Map 以 [key, value] 的形式保存。

参考资料