likes
comments
collection
share

js 中的强弱引用 -- map、weakMap、set、weakSet

作者站长头像
站长
· 阅读数 42

前言

每次一说到算法就是一个头两个大,但是没办法为了有所提升还是得硬着头皮学下去 😭

刚好今天看到了集合与字典那章,其中提到了 weakMap 和 weakSet

书上说 weakMap 是对象的弱引用而不是强引用,并且可以被垃圾回收,当时看到这句话我困惑了许久

什么是强引用,什么又是弱引用? 那今天更文的主题不就有了嘛 😉

Set、Map、WeakSet、WeakMap 就让我依次来说道说道。

Set - 集合

Set 是一种无序且唯一的数据集合,其中的元素不会重复。集合 以 [value, value]的形式储存元素,它可以用来存储一组需要快速查找的数据,例如过滤重复的数据,或者获取一组数据的交集、并集、差集等操作。

Set 本身是一种构造函数,用来生成 Set 数据结构。 Set 对象允许你储存任何类型的唯一值,无论是原始值或者是对象引用。向 Set 加入值的时候,不会发生类型转换

const s = new Set()
[1, 2, 3, 4, 3, 2, 1].forEach(x => s.add(x))

for (let i of s) {
    console.log(i)    // 1 2 3 4
}

// 去重数组的重复对象
let arr = [1, 2, 3, 2, 1, 1]
[... new Set(arr)]    // [1, 2, 3]

const set = new Set()
const obj = {name: 'haha'}

// 添加元素
set.add(1)
set.add('hello')
set.add({ name: 'John' })
set.add(obj)


// 再次添加相同的元素
set.add(1)
set.add('hello')
set.add(obj)

// 输出 Set 对象中的元素个数
console.log(set.size) // 猜一下最后的 set 内容

看一下最后的输出是结果:

js 中的强弱引用 -- map、weakMap、set、weakSet

我们向 Set 对象中添加了两个具有相同属性值的对象 { name: 'John' },但它们是两个不同的对象,因此它们被视为两个不同的元素。

而我们向 Set 对象中添加了一个对象 { name: 'haha' },因为只有一个对象具有该属性值,所以 Set 对象中只包含一个该对象的引用。

Set 的使用方法

Set 的使用非常简单,使用构造函数来创建一个 Set 对象,并使用 add() 方法向其中添加元素。

let mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(3);

Set 的操作方法:

操作方法方法描述
add()向 Set 中添加元素
has()判断一个元素是否存在于 Set 中
delete()删除set中的指定元素
clear()清空所有元素

可以通过 size 属性获取 Set 中元素的个数

由于 Set 对象内置了迭代器,所以我们可以遍历 Set 对象中的元素:

迭代方法方法描述
keys()返回一个包含集合中所有键的迭代器
values()返回一个包含集合中所有值得迭代器
entries()返回一个包含Set对象中所有元素得键值对迭代器

同样的我们可以使用 for...of 、forEach() 等方法来遍历 Set 对象中的元素。

const set = new Set([1, 2, 3]);

// 使用 for...of 循环来遍历 Set 对象中的元素
for (const element of set) {
  console.log(element);
}

// 使用 forEach() 方法来遍历 Set 对象中的元素
set.forEach(element => {
  console.log(element);
});

// 可以使用 entries() 方法来获取 Set 对象中每个元素的键值对,然后进行遍历
const set = new Set([1, 2, 3]);

for (const [key, value] of set.entries()) {
  console.log(key, value);
}

Set 的应用场景

Set 主要适用于需要快速查找并过滤重复数据的场景,例如:

  • 过滤重复的数组元素:
let arr = [1, 2, 3, 2, 1];
let mySet = new Set(arr);
let newArr = Array.from(mySet); // [1, 2, 3]

通过 Array.from() 将 Set 结构转为数组,同时确保数组中的值得唯一性,实现数组去重的效果。

将 Set 转为数组之后可以使用数组上的 map、filter 方法,所以通过这个特性我们可以很容易实现交集(Intersect)、并集(Union)、差集(Difference)的操作

  • 获取两个数组的交集、并集、差集等操作:
let arr1 = [1, 2, 3];
let arr2 = [2, 3, 4];
let set1 = new Set(arr1);
let set2 = new Set(arr2);

// 交集
let intersection = new Set([...set1].filter(x => set2.has(x)));

// 并集
let union = new Set([...set1, ...set2]);

// 差集
let difference = new Set([...set1].filter(x => !set2.has(x)));

Map - 字典

Map 是一组键值对的有序列表,本质上时一个键值对的集合,在 Map 中每个键对应一个值,并且要求每个键只能是唯一的。

Map 中的键和值可以是任何数据类型,甚至连函数也可以,与集合不同地时 Map 是以 [key, value] 的形式储存,可用它来存储需要按照键进行查找的数据,例如存储用户信息、配置信息等。

Map 的使用方式

Map 的使用方式也非常简单,可以直接使用构造函数来创建一个 Map 对象,并使用 set() 方法向其中添加键值对,添加的元素必须是以对象的形式添加,通过 get() 获取元素。

let myMap = new Map();
myMap.set("key1", "value1");
myMap.set("key2", "value2");
myMap.set("key3", "value3");

js 中的强弱引用 -- map、weakMap、set、weakSet

Map 的操作方法:

操作方法方法描述
set()向 Map 中添加键值对,并返回整个 Map 结构
get()获取指定键对应的值,若不存在会返回 undefined
has()判断一个键是否存在于 Map 中
delete()删除Map中指定的键值对,操作成功返回 true,失败返回 false
clear()清空Map中的所有键值对,没有返回值

通过 size 属性可以获取 Map 中键值对的个数。

Map 跟 Set 一样内置了迭代器,所以我们可以遍历 Map 对象中的元素:

迭代方法方法描述
keys()返回一个包含集合中所有键的迭代器
values()返回一个包含集合中所有值得迭代器
entries()返回一个包含Set对象中所有元素得键值对迭代器

我们可以对 Map 进行遍历,

const map = new Map([
    ['name', 'haha'],
    ['age', 18]
]);
console.log(map.entries())    // MapIterator {"name" => "haha", "age" => 18}
console.log(map.keys()) // MapIterator {"name", "age"}

Map 类型转换

Map 转数组

通过扩展运算符可以快速的将 Map 快速转为数组,实现类型转换

const map = new Map([
    ['name', 'haha'],
    ['age', 18]
]);
let arr =[...map]

在这里我们可以看到,Map 中的每一项都被转为了 [ key, value ] 的形式

js 中的强弱引用 -- map、weakMap、set、weakSet

数组 转 Map

同样的数组也可以转换成 Map,先来试试看数字类型的数组能不能成功转换为 Map

let arr =[1,2,3,4,5,6,7,8]
let myMap = new Map(arr)

这个时候居然报错了,value 1 不是一个完整的对象,因为 Map 只能以键值对的形式进行存储。

js 中的强弱引用 -- map、weakMap、set、weakSet

那按照 [key,value] 的形式呢

const arr = [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']];

const map = new Map(arr);

console.log(map);

js 中的强弱引用 -- map、weakMap、set、weakSet

这到底是为什么呢?

答:Map 对象是一种键值对的集合,其中键和值可以是任何类型的数据,包括数组。

因此,可以使用包含多个键值对的数组来初始化 Map 对象。

在这种情况下,数组中的每个元素都应该是一个包含两个元素的子数组,第一个元素是键,第二个元素是值。

所以 [ value, value] 的方式只能用来实现 Set

Map 转 Object

通过 for ... of 遍历利用 Map 中的每个键,将键值对加入对象中

let obj = {}
for (let [k, v] of map) {
  obj[k] = v
}

Object 转 Map

对象转 Map 就更简单了,将对象中的每一项都添加到 Map 对象中

function objToMap(obj) {
    let map = new Map()
    for (let key of Object.keys(obj)) {
        map.set(key, obj[key])
    }
    return map
}

objToMap({'name': 'haha', 'age': 18}) // Map {"name" => "haha", "age" => 18}

Map 转 JSON

在转换的过程中现将 map 转换为数组,再利用 JSON.stringify() 转换为JSON字符串

function mapToJson(map) {
    return JSON.stringify([...map])
}

let map = new Map().set('name', 'haha').set('age', 18)
mapToJson(map)    // [["name","haha"],["age", 18]

js 中的强弱引用 -- map、weakMap、set、weakSet

Map 的应用场景

Map 主要适用于需要按照键进行查找并存储一些数据的场景:

特别是需要进行频繁的增删改查操作时使用 Map 的性能要比 Object 好,更加高效。

  • 存储用户信息:
let user1 = { name: "Alice", age: 18 };
let user2 = { name: "Bob", age: 20 };
let user3 = { name: "Charlie", age: 2 };

let userMap = new Map();
userMap.set(user1, "user1");
userMap.set(user2, "user2");
userMap.set(user3, "user3");

console.log(userMap.get(user1)); // "user1"
  • 存储配置信息:
let configMap = new Map([  
  ["key1", "value1"],
  ["key2", "value2"],
  ["key3", "value3"]
]);

强引用 & 弱引用

在介绍下面两个weak 类型之前,我先介绍两个相关的概念,强引用 & 弱引用。

强引用

强引用是指 JavaScript 中,变量或对象可以被其他变量或对象引用,并且这种引用会对变量或对象产生强制性的影响。只有当引用被清除时,变量或对象才会被垃圾回收器回收。

const myArr = [];
const obj1 = { name: 'John' };
const obj2 = { name: 'Jane' };
myArr.push(obj1, obj2); // myArr对obj1和obj2形成了强引用

myArr数组对obj1和obj2对象形成了强引用。当我们不再需要这两个对象时,我们必须手动删除引用。

// 手动解除数组对这两个对象的引用
myArr[0] = null
myArr[1] = null

只有当手动解除 myArr 数组对这两个对象的引用关系后,垃圾回收机制才会释放 obj1 和 obj2 所占用的内存。

如果大量数据被数组强引用,而没有进行解绑操作,可能会造成内存泄漏,从而产生性能问题。

弱引用

JavaScript 中的弱引用是指在 JavaScrip t中,变量或对象可以被其他变量或对象引用,但这种引用不会对变量或对象产生强制性的影响。弱引用的特点是当一个变量或对象不再被引用时,它将被垃圾回收器回收。

const myWeakMap = new WeakMap();
const obj1 = { name: 'John' };
const obj2 = { name: 'Jane' };
myWeakMap.set(obj1, 'info1');
myWeakMap.set(obj2, 'info2');

myWeakMap 对象对 obj1 和 obj2 对象形成了弱引用。

因为这是弱引用,所以即使没有其他对象对它们进行强引用,它们也可能在任何时刻被回收。

强引用和弱引用是为了解决内存管理的问题而产生的。两者区别就是:

强引用数据被删除时,需要手动解除引用,而弱引用则可以等待垃圾回收机制自动清除

WeakSet

WeakSet 是一种弱引用集合,其中的元素必须是对象。它可以用来存储一组对象,并且不会阻止这些对象被垃圾回收机制回收。因此,如果一个对象被从内存中删除了,它也会自动从 WeakSet 中删除。

WeakSet 的主要特点包括:

  • 只能添加对象类型的元素,储存对象引用,不能存放值,而 Set 对象都可以
  • WeakMap 的键名引用的对象是弱引用,当元素没有被其他地方引用时,会自动从 WeakSet 中删除;
  • 没有 size 属性,不能遍历其中的元素;
  • 只能通过 add()、delete() 和 has() 方法操作其中的元素。
  • 不支持 size 属性,因为 WeakSet 的弱引用随时可能会被垃圾回收。
const arr = [[1, 2], [3, 4]]
const weakset = new WeakSet(arr)
console.log(weakset)
// {[1,2], [3,4]}

WeakMap

WeakMap 是一种弱引用键值对集合,其中的键必须是对象。它可以用来存储需要按照键进行查找的数据,并且不会阻止这些键被垃圾回收机制回收。因此,如果一个键被从内存中删除了,它也会自动从 WeakMap 中删除。

WeakMap 的主要特点包括:

  • 只能添加对象类型的键,当键没有在其他地方被引用时,会自动被垃圾回收从内存中清除;
  • 不支持 size 属性,因为 WeakMap 中的键可能会被自动删除。
  • 可以通过 set()、get()、delete() 和 has() 方法操作其中的键值对。

Map 与 WeakMap 的区别

  • Map 的键可以是任意类型,WeakMap 只接受对象作为键(null除外),不接受其他类型的值作为键
  • Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键; WeakMap 的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的
  • 在 Map 中键和值都是强引用关系。只要 Map 对象存在,它所包含的所有键和值都不会被垃圾回收。

而在 WeakMap 中 键是弱引用,值是强引用。当键不再被使用时,会自动被 WeakMap 删除。

  • WeakMap 的弱引用可以避免内存泄漏,同时也使得 WeakMap 不能像 Map 那样被直接遍历。

WeakSet、WeakMap 的使用和应用

WeakSet 和 WeakMap 的使用方式与普通的 Set 和 Map 类似,但需要注意它们只能存储对象,并且不能直接遍历其中的元素或键值对。

WeakSet 和 WeakMap 主要适用于需要存储一些临时数据或缓存数据的场景:

  1. 存储临时数据:例如事件监听器、缓存数据等。这些数据通常只在特定的时段内被使用,之后就会被垃圾回收机制回收。
function process(data) {
  let cache = new WeakSet();
  for (let item of data) {
    if (cache.has(item)) {
      // 如果已经处理过该数据,则跳过
      continue;
    }
    // 处理数据
    cache.add(item);
  }
}
  1. 缓存函数结果:在一些需要缓存数据的场景下,我们可能需要存储一些临时的缓存数据,例如网络请求结果、计算结果等。
function memoize(fn) {
  let cache = new WeakMap();
  return function(...args) {
    if (cache.has(args)) {
      // 如果已经计算过该结果,则直接返回缓存的结果
      return cache.get(args);
    }
    // 计算结果
    let result = fn(...args);
    cache.set(args, result);
    return result;
  }
}
  1. 存储 DOM 元素:在一些需要操作 DOM 元素的场景下,我们可能需要存储一组 DOM 元素,例如事件委托、动态添加元素等。使用 WeakSet 可以避免这些 DOM 元素影响垃圾回收机制的判断。
// 创建 WeakSet 对象
const elements = new WeakSet();

// 添加 DOM 元素到 WeakSet
const element = document.getElementById('example');
elements.add(element);

// 检查 WeakSet 是否包含某个元素
console.log(elements.has(element)); // true

// 当不再需要 DOM 元素时,从 WeakSet 中删除它
elements.delete(element);

Map、WeakMap、Set 和 WeakSet 是 JavaScript 中的四种集合类型,

这些集合类型可以根据具体需求选择使用,例如:

  • 当我们需要存储一组唯一且无序的数据时,可以使用 Set。

  • 如果需要存储一组键值对,并且需要快速查找指定键的值,可以使用 Map。

  • 如果需要存储一组临时保存的对象,并且不需要遍历其中的元素,可以使用 WeakSet。

  • 如果需要存储一组临时保存的键值对,并且不需要遍历其中的键值对,可以使用 WeakMap。

总结

最后再来个小小的总结:

Set 和 Map 是强引用集合,而 WeakSet 和 WeakMap 是弱引用集合:

  • 强引用集合中的元素或键值对只有在被显式删除时才会被回收
  • 弱引用集合中的元素或键可能会在没有其他引用时被自动回收

我们可以根据不同的需求选择相应的数据结构,帮助我们更好地管理和处理数据:

  • Set 可以用来过滤重复数据、获取两个数组之间的交集、并集、差集等操作。
  • Map 可以用来将一个对象映射到另一个对象、存储一组按照特定顺序访问的数据,实现键-值的快速查找。
  • WeakSet 可以用来临时保存一个对象的状态或者在某个对象被销毁时执行一些清理操作,同时不需要遍历其中元素。
  • WeakMap 可以用来临时保存与某个对象相关联的数据或者在某个对象被销毁时自动删除与之相关联的数据,同时不需要遍历其中的键值对。

以上就是所有的内容啦,有啥问题及时补充,希望对大家也有所帮助啊 😉。

参考

浅析 Map 和 WeakMap 区别以及使用场景

介绍下 Set、Map、WeakSet 和 WeakMap 的区别?

转载自:https://juejin.cn/post/7244174211957850172
评论
请登录