WeakMap 和 Map 的区别,WeakMap 原理,为什么能被 GC?

2023-12-29 12:04
文章标签 原理 区别 map gc weakmap

本文主要是介绍WeakMap 和 Map 的区别,WeakMap 原理,为什么能被 GC?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

垃圾回收机制

我们知道,程序运行中会有一些垃圾数据不再使用,需要及时释放出去,如果我们没有及时释放,这就是内存泄露

JS 中的垃圾数据都是由垃圾回收(Garbage Collection,缩写为 GC)器自动回收的,不需要手动释放,它是如何做的呢?

很简单,JS 引擎中有一个后台进程称为垃圾回收器,它监视所有对象,观察对象是否可被访问,然后按照固定的时间间隔周期性的删除掉那些不可访问的对象即可

现在各大浏览器通常用采用的垃圾回收有两种方法:

  • 引用计数
  • 标记清除

引用计数

最早最简单的垃圾回收机制,就是给一个占用物理空间的对象附加一个引用计数器,当有其它对象引用这个对象时,这个对象的引用计数加一,反之解除时就减一,当该对象引用计数为 0 时就会被回收。

该方式很简单,但会引起内存泄漏:

// 循环引用的问题
function temp(){var a={};var b={};a.o = b;b.o = a;
}

这种情况下每次调用 temp 函数,a 和 b 的引用计数都是 2 ,会使这部分内存永远不会被释放,即内存泄漏。现在已经很少使用了,只有低版本的 IE 使用这种方式。

标记清除

V8 中主垃圾回收器就采用标记清除法进行垃圾回收。主要流程如下:

  • 标记:遍历调用栈,看老生代区域堆中的对象是否被引用,被引用的对象标记为活动对象,没有被引用的对象(待清理)标记为垃圾数据。
  • 垃圾清理:将所有垃圾数据清理掉

在我们的开发过程中,如果我们想要让垃圾回收器回收某一对象,就将对象的引用直接设置为 null

var a = {}; // {} 可访问,a 是其引用a = null; // 引用设置为 null
// {} 将会被从内存里清理出去

但如果一个对象被多次引用时,例如作为另一对象的键、值或子元素时,将该对象引用设置为 null 时,该对象是不会被回收的,依然存在

var a = {}; 
var arr = [a];a = null; 
console.log(arr)
// [{}]

如果作为 Map 的键呢?

var a = {}; 
var map = new Map();
map.set(a, '小芳学前端')a = null; 
console.log(map.keys()) // MapIterator {{}}
console.log(map.values()) // MapIterator {"小芳学前端"}

很明显,该对象没有被回收,如果想让 a 置为 null 时,该对象被回收,该怎么做呢?

WeakMap vs Map

ES6 考虑到了这一点,推出了: WeakMap 。它对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)。

Map 相对于 WeakMap :

  • Map 的键可以是任意类型,WeakMap 只接受对象作为键(null除外),不接受其他类型的值作为键
  • Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键; WeakMap 的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的
  • Map 可以被遍历, WeakMap 不能被遍历

下面以 WeakMap 为例,看看它是怎么上面问题的:

var a = {}; 
var map = new WeakMap();
map.set(a, '小芳学前端')
map.get(a)a = null; 

上例并不能看出什么?我们通过 process.memoryUsage 测试一下:

创建一个map.js文件,内容如下:

//map.js
global.gc(); // 0 每次查询内存都先执行gc()再memoryUsage(),是为了确保垃圾回收,保证获取的内存使用状态准确function usedSize() {const used = process.memoryUsage().heapUsed;return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}console.log(usedSize()); // 1 初始状态,执行gc()和memoryUsage()以后,heapUsed 值为 1.64Mvar map = new Map();
var b = new Array(5 * 1024 * 1024);map.set(b, 1);global.gc();
console.log(usedSize()); // 2 在 Map 中加入元素b,为一个 5*1024*1024 的数组后,heapUsed为41.82M左右b = null;
global.gc();console.log(usedSize()); // 3 将b置为空以后,heapUsed 仍为41.82M,说明Map中的那个长度为5*1024*1024的数组依然存在

在当前map.js文件目录执行 node --expose-gc map.js 命令:

其中,--expose-gc 参数表示允许手动执行垃圾回收机制

再创建一个weakmap.js文件,如下:

// weakmap.js
function usedSize() {const used = process.memoryUsage().heapUsed;return Math.round((used / 1024 / 1024) * 100) / 100 + "M";
}global.gc(); // 0 每次查询内存都先执行gc()再memoryUsage(),是为了确保垃圾回收,保证获取的内存使用状态准确
console.log(usedSize()); // 1 初始状态,执行gc()和 memoryUsage()以后,heapUsed 值为 1.64M
var map = new WeakMap();
var b = new Array(5 * 1024 * 1024);map.set(b, 1);global.gc();
console.log(usedSize()); // 2 在 Map 中加入元素b,为一个 5*1024*1024 的数组后,heapUsed为41.82M左右b = null;
global.gc();console.log(usedSize()); // 3 将b置为空以后,heapUsed 变成了1.82M左右,说明WeakMap中的那个长度为5*1024*1024的数组被销毁了

执行 node --expose-gc weakmap.js 命令:

上面代码中,只要外部的引用消失,WeakMap 内部的引用,就会自动被垃圾回收清除。由此可见,有了它的帮助,解决内存泄漏就会简单很多。

最后看一下 WeakMap

WeakMap

WeakMap 对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意

注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

WeakMap 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 的 key 是不可枚举的。

属性:

  • constructor:构造函数

方法:

  • has(key):判断是否有 key 关联对象
  • get(key):返回key关联对象(没有则则返回 undefined)
  • set(key):设置一组key关联对象
  • delete(key):移除 key 的关联对象
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();myWeakmap.set(myElement, {timesClicked: 0});myElement.addEventListener('click', function() {let logoData = myWeakmap.get(myElement);logoData.timesClicked++;
}, false);

除了 WeakMap 还有 WeakSet 都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏

参考

你不知道的 WeakMap

 添加好友备注【进阶学习】拉你进技术交流群

这篇关于WeakMap 和 Map 的区别,WeakMap 原理,为什么能被 GC?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/549383

相关文章

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

C# Semaphore与SemaphoreSlim区别小结

《C#Semaphore与SemaphoreSlim区别小结》本文主要介绍了C#Semaphore与SemaphoreSlim区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录一、核心区别概览二、详细对比说明1.跨进程支持2.异步支持(关键区别!)3.性能差异4.API 差

Java中自旋锁与CAS机制的深层关系与区别

《Java中自旋锁与CAS机制的深层关系与区别》CAS算法即比较并替换,是一种实现并发编程时常用到的算法,Java并发包中的很多类都使用了CAS算法,:本文主要介绍Java中自旋锁与CAS机制深层... 目录1. 引言2. 比较并交换 (Compare-and-Swap, CAS) 核心原理2.1 CAS

Java 队列Queue从原理到实战指南

《Java队列Queue从原理到实战指南》本文介绍了Java中队列(Queue)的底层实现、常见方法及其区别,通过LinkedList和ArrayDeque的实现,以及循环队列的概念,展示了如何高效... 目录一、队列的认识队列的底层与集合框架常见的队列方法插入元素方法对比(add和offer)移除元素方法

C# GC回收的方法实现

《C#GC回收的方法实现》本文主要介绍了C#GC回收的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 一、什么是 GC? 二、GC 管理的是哪部分内存? 三、GC 什么时候触发?️ 四、GC 如何判断一个对象是“垃圾

SQL 注入攻击(SQL Injection)原理、利用方式与防御策略深度解析

《SQL注入攻击(SQLInjection)原理、利用方式与防御策略深度解析》本文将从SQL注入的基本原理、攻击方式、常见利用手法,到企业级防御方案进行全面讲解,以帮助开发者和安全人员更系统地理解... 目录一、前言二、SQL 注入攻击的基本概念三、SQL 注入常见类型分析1. 基于错误回显的注入(Erro

Spring IOC核心原理详解与运用实战教程

《SpringIOC核心原理详解与运用实战教程》本文详细解析了SpringIOC容器的核心原理,包括BeanFactory体系、依赖注入机制、循环依赖解决和三级缓存机制,同时,介绍了SpringBo... 目录1. Spring IOC核心原理深度解析1.1 BeanFactory体系与内部结构1.1.1

MySQL 批量插入的原理和实战方法(快速提升大数据导入效率)

《MySQL批量插入的原理和实战方法(快速提升大数据导入效率)》在日常开发中,我们经常需要将大量数据批量插入到MySQL数据库中,本文将介绍批量插入的原理、实现方法,并结合Python和PyMySQ... 目录一、批量插入的优势二、mysql 表的创建示例三、python 实现批量插入1. 安装 PyMyS

深入理解Redis线程模型的原理及使用

《深入理解Redis线程模型的原理及使用》Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的,整个线程模型可以理解为还是以单线程为主,基于这种单线程为主的线程模型,不同客户端的... 目录1 Redis是单线程www.chinasem.cn还是多线程2 Redis如何保证指令原子性2.

Java Map排序如何按照值按照键排序

《JavaMap排序如何按照值按照键排序》该文章主要介绍Java中三种Map(HashMap、LinkedHashMap、TreeMap)的默认排序行为及实现按键排序和按值排序的方法,每种方法结合实... 目录一、先理清 3 种 Map 的默认排序行为二、按「键」排序的实现方式1. 方式 1:用 TreeM