JS Set与Map

本文详细介绍了JavaScript中的Set和Map数据结构,包括它们的基本用法、操作方法、特性以及在实际编程中的应用。Set用于存储唯一值,Map则允许键值对,两者都提供了丰富的操作和遍历方式。此外,还提到了WeakSet和WeakMap,它们分别用于弱引用的场景。文章展示了如何利用Set和Map进行数组去重、对象存储以及与其他数据结构的转换,并探讨了它们在垃圾回收机制中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概要

  1. 数组在JavaScript中的使用正如其他语言的数组一样,但缺少更多类型的集合导致数组也经常被当作队列与栈来使用。
  2. 数组只使用了数值型的索引,而如果非数值型的索引是必要的,开发者便会使用非数组的对象。
  3. 这种技巧引出了非数组对象的定制实现,即 Set 与 Map。

Set

基本用法
类似于数组,但其成员的值都是唯一的。
创建方法
Set本身是一个构造函数,用来生成Set实例。

const s = new Set();
[2, 3, 4, 5, 2, 2].forEach((x) => s.add(x));
for (let i of s) {
  console.log(i); // 2 3 4 5
}

add( )方法可以添加元素到Set实例中,但不会添加重复的值。

初始化
Set函数可以接受一个数组(或具有Iterable接口的其他数据结构)作为参数,来初始化Set实例。

let set = new Set([1, 2, 3, 4, 4]);
console.log([...set]); // [1, 2, 3, 4]

应用
去掉数组中的重复值

let items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(...items); // 1 2 3 4 5

去掉字符串中重复字符

let str = "abbcddef";
console.log([...new Set(str)].join("")); // abcdef

内部比较机制

  1. 向Set实例加入值时不会发生类型转换,因此5和’5’是不同的。
  2. 利用算法“Same-value equality”来判断加入的值和已有的值是否相等,类似于Object.is()方法,如果相等则不加入。
  3. 两个对象总是不相等的。
let set = new Set();
set.add({});
set.add({});
console.log(set.size); // 2

let a = {};
let b = a;
set.add(a);
set.add(b);
console.log(set.size); // 1
console.log([...set]); // [ {} ]

属性
constructor,构造函数,默认就是Set函数。
size,返回Set实例的成员数量。

操作方法

  1. add(value),添加值,返回该Set实例的引用。
  2. delete(value),删除值,返回一个布尔值,表示删除是否成功。
  3. has(value),返回一个布尔值,表示该值是否是Set实例的成员。
  4. clear( ),清除所有成员,没有返回值。
let set = new Set();
set.add(1).add(2).add(2);
console.log(set); // Set(2) { 1, 2 }

console.log(set.has(2)); // true
console.log(set.has(3)); // false

set.delete(1);
console.log(set); // Set(1) { 2 }

set.clear();
console.log(set); // Set(0) {}

遍历方法

keys( ) 返回键名的遍历器。
values( ) 返回键值的遍历器。

let set1 = new Set(["red", "green", "blue"]);
for (let item of set1.keys()) {
  console.log(item); // red green blue
}
for (let item of set1.values()) {
  console.log(item); // red green blue
}

// set结构实例的默认遍历器生成函数就是values()方法
for (let item of set1) {
  console.log(item); // red green blue
}

由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

entries() 返回键值对的遍历器。

console.log(set1.entries());
/*
[Set Entries] {
  [ 'red', 'red' ],
  [ 'green', 'green' ],
  [ 'blue', 'blue' ]
}
*/

forEach( ) 使用回调函数遍历每个成员,没有返回值。

set1.forEach((value) => console.log(value.toUpperCase()));
// RED GREEN BLUE
set1.forEach((value, key, s) => {
  console.log(s.size);
  console.log(value);
  // 3 red 3 green 3 blue
});

遍历的应用
扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。

let arr = [3, 4, 5, 5, 3, 2, 4, 2, 1, 1];
arr = [...new Set(arr)];
console.log(arr); // [ 3, 4, 5, 2, 1 ]

Set实例可以间接的使用数组的map和filter方法。

let set = new Set([1, 2, 3, 4]);
set = new Set([...set].map((x) => x * 2));
console.log(set); // Set(4) { 2, 4, 6, 8 }
set = new Set([...set].filter((x) => x % 3 == 0));
console.log(set); // Set(1) { 6 }

扩展运算符、map、filter和 Set 结构相结合,实现并集、交集、差集。

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
//并集
let union = new Set([...a], [...b]);
console.log(union); // Set(3) { 1, 2, 3 }
//交集
let interect = new Set([...a].filter((x) => b.has(x)));
console.log(interect); // Set(2) { 2, 3 }
//差集
let diffs = new Set([...a].filter((x) => !b.has(x)));
console.log(diffs); // Set(1) { 1 }

WeakSet

与Set的区别

  1. WeakSet的成员只能是对象。
  2. WeakSet中的对象都是弱引用。
  3. 垃圾回收机制不考虑WeakSet对该对象的引用。
  4. 如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占的内存。

应用场景

  1. WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。
  2. 只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。

ES6 规定 WeakSet 不可遍历。

Map

Map类型是键值对的有序列表,而键和值都可以是任意类型。
相较于Object

  1. Object只能用字符串当作键,其他类型作为键时会自动转换为字符串。
  2. 字符串-值: Map可以直接使用各种类型的值(包括对象)作为键。
  3. 值-值 :Object重在表达对象,Map重在表达数据结构–字典。

基本用法
set(key, value) 设置(添加)键值对。
get(key) 通过key获取对应值。

类似Set的方法和属性

  • has(key)
  • delete(key)
  • clear( )
  • size
let m = new Map();
let o = { p: "hello world" };
m.set(o, "content");
console.log(m.size); // 1
console.log(m.get(o)); // content
console.log(m.has(o)); // true
console.log(m.delete(o)); // true
console.log(m.has(o)); // false
console.log(m.clear()); // undefined

Map构造函数可以接收一个数组作为参数。

  1. 该数组的成员是一组表示键值对的数组。
  2. 不仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以作为Map构造函数的参数。
  3. Set和Map都可以用来生成新的 Map。
let map = new Map([
  ["name", "zhang"],
  ["title", "Author"],
]);
console.log(map.size); // 2
console.log(map.has("name")); // true
console.log(map.get("name")); // zhang
console.log(map.has("title")); // true
console.log(map.get("title")); // Author

只有对同一个对象的引用,Map才认为是同一个键。
Map的键实际上是和内存地址绑定的,只要内存地址不一样,就是为两个键。

const k1 = {};
const k2 = {};
mm.set(k1, 100);
mm.set(k2, 200);
console.log(mm.get(k1), mm.get(k2)); // 100 200

如果Map的键是一个简单类型的值(数值,字符串,布尔值),只要两个值严格相等,Map就认为是同一个键。

mm.set(-0, 123);
console.log(mm.get(+0)); // 123
console.log(-0 == +0); // true

mm.set(true, 1);
mm.set("true", 2);
console.log(mm.get(true)); // 1
console.log(true == "true"); // false

mm.set(undefined, 3);
mm.set(null, 4);
mm.set(undefined, 10); // 会覆盖之前的(undefined, 3)
console.log(mm.get(undefined)); // 10
console.log(mm.get(null)); // 4

mm.set(NaN, 111);
mm.set(NaN, 123);
console.log(mm.get(NaN)); // 123
console.log(NaN == NaN); // false

遍历方法
Map 结构原生提供三个遍历器生成函数和一个遍历方法。

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • forEach():遍历 Map 的所有成员。
  • Map 结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。
const map = new Map([
  ["F", "no"],
  ["T", "yes"],
]);

for (let [key, value] of map.entries()) {
  console.log(key, value); // F no T yes
}

for (let [key, value] of map) {
  console.log(key, value); // F no T yes
}

map.forEach((x, y) => console.log(y, x)); // F no T yes

与Set类似,Map也可以利用…(展开运算符)转换为数组,从而利用map( )、filter( )等方法。

const m1 = new Map([
  ["a", 10],
  ["b", 12],
  ["c", 14],
  ["d", 16],
]);

m1.forEach((v, k, m) => {
  v += 1;
  console.log(k, v, m.size); //a 11 4, b 13 4, c 15 4, d 17 4
});

m1.forEach((v, k, m) => m.set(k, v + 1));
console.log(m1); // Map(4) { 'a' => 11, 'b' => 13, 'c' => 15, 'd' => 17 }
const map0 = new Map().set(1, "a").set(2, "b").set(3, "c");
console.log(map0); // Map(3) { 1 => 'a', 2 => 'b', 3 => 'c' }

const map1 = new Map([...map0].filter(([k, v]) => k < 3));
console.log(map1); // Map(2) { 1 => 'a', 2 => 'b' }

const map2 = new Map([...map0].map(([k, v]) => [k * 2, "_" + v]));
console.log(map2); // Map(3) { 2 => '_a', 4 => '_b', 6 => '_c' }
console.log(map0); // Map(3) { 1 => 'a', 2 => 'b', 3 => 'c' }

与其他数据结构的相互转换
Map转换为数组

const myMap = new Map().set(true, 7).set({ foo: 3 }, ["abc"]);
let myArr = [...myMap];
console.log(myArr); // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

数组转换为Map

const myMap = new Map([
  [true, 7],
  [{ foo: 3 }, ["abc"]],
]);
console.log(myMap); // Map(2) { true => 7, { 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 myMap = new Map().set("yes", true).set("no", false);
let obj = strMapToObj(myMap);
console.log(obj, obj.yes, obj.no); // [Object: null prototype] { yes: true, no: false } true false

对象转换为Map

function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}
let myMap = objToStrMap({ yes: true, no: false });
console.log(myMap, myMap.get("yes"), myMap.get("no")); // Map(2) { 'yes' => true, 'no' => false } true false

Map转换为JSON
Map的键名都是字符串,可以选择转换为对象JSON。

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k, v] of strMap) {
    obj[k] = v;
  }
  return obj;
}
function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

function strMapToJSON(strMap) {
  //先将Map转换为对象,在通过对象生成JSON。
  return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set("yes", true).set("no", false);
let json1 = strMapToJSON(myMap);
console.log(json1); // {"yes":true,"no":false}

Map转换为JSON
Map的键名有非字符串,可以转换为数组JSON。

function mapToArrayJSON(map) {
  return JSON.stringify([...map]);
}
let yourMap = new Map().set(true, 7).set({ foo: 3 }, ["abc"]);
let json2 = mapToArrayJSON(yourMap);
console.log(json2); // [[true,7],[{"foo":3},["abc"]]]

JSON转换为Map
正常情况下所有键名都是字符串

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}
let theMap = jsonToStrMap('{"yes":true,"no":false } ');
console.log(theMap); // Map(2) { 'yes' => true, 'no' => false }

WeakMap

WeakMap与Map的区别

  1. WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
  2. WeakMap的键名所指向的对象,不计入垃圾回收机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值