一张纸搞懂JS系列(3)之垃圾回收机制,内存泄漏,闭包
javascript栏目为大家介绍垃圾回收机制,内存泄漏,闭包的内容,快端小板凳来看看啦。
写在最前面:这是javascript栏目我即将开始写的一个系列,主要是在框架横行的时代,虽然上班用的是框架,但是对于面试,以及技术进阶,JS基础知识的铺垫是锦上添花,也是不得不学习的一块知识,虽然开汽车的不需要很懂汽车,只需要掌握汽车的常用功能即可。但是如果你懂汽车,那你也能更好地开车,同理。当然,一篇文章也不会光光只讲一个知识点,一般会将有关联的知识点串联起来,一边记录自己的学习,一边分享自己的学习,互勉!如果可以的话,也请给我点个赞,你的点赞也能让我更加努力地更新!
概览
- 食用时间: 6-12分钟
- 难度: 简单,别跑,看完再走
垃圾回收机制
前面一篇博客主要讲解了内存的分配和使用(栈内存与堆内存,深拷贝与浅拷贝),使用完了以后,当然是要将不使用的内存归还,就像将手机上不使用的软件从后台清除,可以提升手机的运行速度,不然越来越多,迟早会卡, JS
也是一样的。
每隔一段时间, JS
的垃圾收集器都会对变量进行“巡逻”,就和保安巡逻园区一样,让不相干的人赶紧走。当一个变量不被需要了以后,它就会把这个变量所占用的内存空间所释放,这个过程就叫做垃圾回收
JS
的垃圾回收算法分为两种,引用计数法和标记清除法
-
引用计数法
引用计数法是最初级的垃圾回收算法,已经被现代浏览器所淘汰了。在学习引用计数法之前,需要首先对引用有一定的概念,你可以认为它就是对当前变量所指向的那块内存地址的描述,有点类似于JS引用数据类型的内存指向的概念,先来看一行代码:
var obj={name:'jack'};复制代码
登录后复制当我们在给
obj
赋值的同时,其实就创建了一个指向该变量的引用,引用计数为1,在引用计数法的机制下,内存中的每一个值都会对应一个引用计数而当我们给
obj
赋值为null
时,这个变量就变成了一块没用的内存,那么此时,obj
的引用计数将会变成 0,它将会被垃圾收集器所回收,也就是obj
所占用的内存空间将会被释放我们知道,函数作用域的生命周期是很短暂的,在函数执行完毕之后,里面的变量基本是没用的变量了,不清除的后果就是该内存垃圾没有被释放,依然霸占着原有的内存不松手,就会容易引发内存泄漏,先来看一段代码以及运行结果:
function changeName(){ var obj1={}; var obj2={}; obj1.target=obj2; obj2.target=obj1; obj1.age=15; console.log(obj1.target); console.log(obj2.target); } changeName();复制代码
登录后复制 我们可以看到,obj1.target
和obj2.target
存在互相引用的情况,因为在改变obj1.age
的同时,obj1.target.age
和obj2.target.age
也同时都被影响到了,它们所指向的引用计数是一致的在函数执行完毕的时候,
obj1
和obj2
还是活的好好地,因为obj1.target
和obj2.target
的引用计数在执行完毕之后,仍然是 1 ,明明函数执行完毕,但是这种垃圾依然存在,这种函数定义多了,内存泄漏也会是无法避免的 标记清除法
上面的引用计数法的弊端已经很明显了,那么,现在所要说的标记清除法就不存在这样子的问题。因为它采用的判断标准是看这个对象是否可抵达,它主要分为两个阶段,标记阶段和清除阶段:
标记阶段
垃圾收集器会从根对象(Window对象)出发,扫描所有可以触及的对象,这就是所谓的可抵达
清除阶段 在扫描的同时,根对象无法触及(不可抵达)的对象,就是被认为不被需要的对象,就会被当成垃圾清除
现在再来看下上面的代码
function changeName(){ var obj1={}; var obj2={}; obj1.target=obj2; obj2.target=obj1; obj1.age=15; console.log(obj1.target); console.log(obj2.target); } changeName();复制代码
登录后复制在函数执行完毕之后,函数的声明周期结束,那么现在,从
Window对象
出发,obj1
和obj2
都会被垃圾收集器标记为不可抵达,这样子的情况下,互相引用的情况也会迎刃而解。
内存泄漏
该释放的内存垃圾没有被释放,依然霸占着原有的内存不松手,造成系统内存的浪费,导致性能恶化,系统崩溃等严重后果,这就是所谓的内存泄漏
闭包
定义与特性
闭包是指有权访问另一个函数作用域中的变量的函数。至于为什么有权访问,主要是因为作用域嵌套作用域,也就是所谓的作用域链,关于作用域链不清楚的可以看我的第一篇博客一文搞懂JS系列(一)之编译原理,作用域,作用域链,变量提升,暂时性死区,就是因为作用域链的存在,所以内部函数才可以访问外部函数中定义的变量 ,作用域链是向外不向内的,探出头去,向外查找,而不是看着锅里,所以外部函数是无法访问内部函数定义的变量的。并且,还有一个特性就是将闭包内的变量始终保持在内存中。
前面的作用域向外不向内,这里就不再做过多解释了,我们主要来看我后面说的特性,那就是闭包内的变量始终保存在内存中
来看一下阮一峰教程当中的一个例子
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ console.log(n); } return f2; } var result=f1(); //等同于return f2(); result(); // 999 nAdd(); result(); // 1000 nAdd(); result(); // 1000复制代码
登录后复制从输出结果就可以看得出来,这个变量
n
就一直保存在内存中,那么,为什么会这样子呢,我们现在就来逐步地分析代码① 首先
f1()
作为f2()
的父函数,根据作用域链的规则,nAdd()
方法以及f2()
方法中可以正常访问到n
的值②
f2()
被赋予了一个全局变量,可能这里大家就会开始产生疑惑了,这个f2()
不是好好地定义在了f1()
函数中吗,这不是扯淡吗,那么,先看下面的这句var result=f1();
,这个result
很明显是被赋予了一个全局变量,这应该是没有任何争议的,那么,接着来看这个f1()
,可以看到最后,是一句return f2;
,看到这里,想必大家也已经想明白了,这个f2()
被赋予了一个全局变量③ 已经明白了上面的这一点以后,根据上面垃圾回收机制所提及到的标记清除法,这个
f2()
始终是可以被根对象Window
访问到的,所以 f2 将始终存在于内存之中,而 f2 是依赖于 f1 ,因此 f1 也将始终存在于内存当中,那么,n
的值也就自然始终存在于内存当中啦④ 还有一点需要注意的就是为什么我们可以直接执行
nAdd()
,这是因为在nAdd()
的前面没有使用var
,因此nAdd()
是一个全局函数而不是局部函数所以,闭包的变量会常驻内存,滥用闭包容易造成内存泄漏,特别是在 IE 浏览器下,2020年了,应该没人使用 IE 了吧(小声bb),解决办法就是在退出函数之前,将不使用的局部变量全部删除,这也是上面讲了垃圾回收机制 => 内存泄漏,再讲到闭包的原因,我会尽量将有关联性的知识点一起讲了,也方便大家学习和加深印象。
系列目录
一张纸懂JS系列(1)之编译原理,作用域,作用域链,变量提升,暂时性死区
一张纸搞懂JS系列(2)之JS内存生命周期,栈内存与堆内存,深浅拷贝
一张纸搞懂JS系列(3)之垃圾回收机制,内存泄漏,闭包
相关免费学习推荐:javascript(视频)
以上是一张纸搞懂JS系列(3)之垃圾回收机制,内存泄漏,闭包的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

C++Lambda表达式支持闭包,即保存函数作用域变量并供函数访问。语法为[capture-list](parameters)->return-type{function-body}。capture-list定义要捕获的变量,可以使用[=]按值捕获所有局部变量,[&]按引用捕获所有局部变量,或[variable1,variable2,...]捕获特定变量。Lambda表达式只能访问捕获的变量,但无法修改原始值。

闭包是一种嵌套函数,它能访问外层函数作用域的变量,优点包括数据封装、状态保持和灵活性。缺点包括内存消耗、性能影响和调试复杂性。此外,闭包还可以创建匿名函数,并将其作为回调或参数传递给其他函数。

Valgrind通过模拟内存分配和释放来检测内存泄漏和错误,使用步骤如下:安装Valgrind:从官方网站下载并安装适用于您操作系统的版本。编译程序:使用Valgrind标志(如gcc-g-omyprogrammyprogram.c-lstdc++)编译程序。分析程序:使用valgrind--leak-check=fullmyprogram命令分析已编译的程序。检查输出:Valgrind将在程序执行后生成报告,显示内存泄漏和错误信息。

内存泄漏会导致Go程序内存不断增加,可通过:关闭不再使用的资源,如文件、网络连接和数据库连接。使用弱引用防止内存泄漏,当对象不再被强引用时将其作为垃圾回收目标。利用go协程,协程栈内存会在退出时自动释放,避免内存泄漏。

C++中内存泄漏是指程序分配了内存但忘记释放,导致内存无法被重用。调试技术包括使用调试器(如Valgrind、GDB)、插入断言和使用内存泄漏检测器库(如Boost.LeakDetector、MemorySanitizer)。通过实践案例展示了使用Valgrind检测内存泄漏,并提出了避免内存泄漏的最佳做法,包括:始终释放分配的内存、使用智能指针、使用内存管理库和定期进行内存检查。

Java中的闭包允许内部函数访问外部的作用域变量,即使外部函数已经退出。通过匿名内部类实现,内部类持有一个外部类的引用,使外部变量保持活动。闭包增强了代码灵活性,但需要注意内存泄漏风险,因为匿名内部类对外部变量的引用会保持这些变量的活动状态。

Go语言函数闭包在单元测试中发挥着至关重要的作用:捕获值:闭包可以访问外部作用域的变量,允许在嵌套函数中捕获和重用测试参数。简化测试代码:通过捕获值,闭包消除了对每个循环重复设置参数的需求,从而简化了测试代码。提高可读性:使用闭包可以组织测试逻辑,使测试代码更清晰、更易于阅读。

要查找C++中的内存泄漏,可以利用Valgrind和AddressSanitizer。Valgrind动态检测泄漏,显示地址、大小和调用栈。AddressSanitizer是一个Clang编译器插件,检测内存错误和泄漏。要启用ASan泄漏检查,请在编译时使用--leak-check=full选项,该选项将在程序运行后报告泄漏。
