常见的JavaScript内存泄露
什么是内存泄露
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
内存泄漏通常情况下只能由获得程序源代码的程序员才能分析出来。然而,有不少人习惯于把任何不需要的内存使用的增加描述为内存泄漏,即使严格意义上来说这是不准确的。
————wikipedia
意外的全局变量
JavaScript对未声明变量的处理方式:在全局对象上创建该变量的引用(即全局对象上的属性,不是变量,因为它能通过delete删除)。如果在浏览器中,全局对象就是window对象。
如果未声明的变量缓存大量的数据,会导致这些数据只有在窗口关闭或重新刷新页面时才能被释放。这样会造成意外的内存泄漏。
<span style="font-size: 14px;">function foo(arg) {<br> bar = "this is a hidden global variable with a large of data";<br>}<br></span>
等同于:
<span style="font-size: 14px;">function foo(arg) {<br> window.bar = "this is an explicit global variable with a large of data";<br>}<br></span>
另外,通过this创建意外的全局变量:
<span style="font-size: 14px;">function foo() {<br> this.variable = "potential accidental global";<br>}<br><br>// 当在全局作用域中调用foo函数,此时this指向的是全局对象(window),而不是'undefined'<br>foo();<br></span>
解决方法:
在JavaScript文件中添加'use strict',开启严格模式,可以有效地避免上述问题。
<span style="font-size: 14px;">function foo(arg) {<br> "use strict" // 在foo函数作用域内开启严格模式<br> bar = "this is an explicit global variable with a large of data";// 报错:因为bar还没有被声明<br>}<br></span>
如果需要在一个函数中使用全局变量,可以像如下代码所示,在window上明确声明:
<span style="font-size: 14px;">function foo(arg) {<br> window.bar = "this is a explicit global variable with a large of data";<br>}<br></span>
这样不仅可读性高,而且后期维护也方便
谈到全局变量,需要注意那些用来临时存储大量数据的全局变量,确保在处理完这些数据后将其设置为null或重新赋值。全局变量也常用来做cache,一般cache都是为了性能优化才用到的,为了性能,最好对cache的大小做个上限限制。因为cache是不能被回收的,越高cache会导致越高的内存消耗。
console.log
console.log:向web开发控制台打印一条消息,常用来在开发时调试分析。有时在开发时,需要打印一些对象信息,但发布时却忘记去掉console.log语句,这可能造成内存泄露。
在传递给console.log的对象是不能被垃圾回收 ♻️,因为在代码运行之后需要在开发工具能查看对象信息。所以最好不要在生产环境中console.log任何对象。
实例------>demos/log.html
<span style="font-size: 14px;"><!DOCTYPE html><br><html lang="en"><br><br><head><br> <meta charset="UTF-8"><br> <meta name="viewport" content="width=device-width, initial-scale=1.0"><br> <meta http-equiv="X-UA-Compatible" content="ie=edge"><br> <title>Leaker</title><br></head><br><br><body><br> <input type="button" value="click"><br> <script><br> !function () {<br> function Leaker() {<br> this.init();<br> };<br> Leaker.prototype = {<br> init: function () {<br> this.name = (Array(100000)).join('*');<br> console.log("Leaking an object %o: %o", (new Date()), this);// this对象不能被回收<br> },<br><br> destroy: function () {<br> // do something....<br> }<br> };<br> document.querySelector('input').addEventListener('click', function () {<br> new Leaker();<br> }, false);<br> }()<br> </script><br></body><br><br></html><br></span>
这里结合Chrome的Devtools–>Performance做一些分析,操作步骤如下:
⚠️注:最好在隐藏窗口中进行分析工作,避免浏览器插件影响分析结果
开启【Performance】项的记录
执行一次CG,创建基准参考线
连续单击【click】按钮三次,新建三个Leaker对象
执行一次CG
停止记录
可以看出【JS Heap】线最后没有降回到基准参考线的位置,显然存在没有被回收的内存。如果将代码修改为:
<span style="font-size: 14px;"> !function () {<br> function Leaker() {<br> this.init();<br> };<br> Leaker.prototype = {<br> init: function () {<br> this.name = (Array(100000)).join('*');<br> },<br><br> destroy: function () {<br> // do something....<br> }<br> };<br> document.querySelector('input').addEventListener('click', function () {<br> new Leaker();<br> }, false);<br> }()<br></span>
去掉console.log("Leaking an object %o: %o", (new Date()), this);语句。重复上述的操作步骤,分析结果如下:
从对比分析结果可知,console.log打印的对象是不会被垃圾回收器回收的。因此最好不要在页面中console.log任何大对象,这样可能会影响页面的整体性能,特别在生产环境中。除了console.log外,另外还有console.dir、console.error、console.warn等都存在类似的问题,这些细节需要特别的关注。
closures(闭包)
当一个函数A返回一个内联函数B,即使函数A执行完,函数B也能访问函数A作用域内的变量,这就是一个闭包——————本质上闭包是将函数内部和外部连接起来的一座桥梁。
<span style="font-size: 14px;">function foo(message) {<br> function closure() {<br> console.log(message)<br> };<br> return closure;<br>}<br><br>// 使用<br>var bar = foo("hello closure!");<br>bar()// 返回 'hello closure!'<br></span>
在函数foo内创建的函数closure对象是不能被回收掉的,因为它被全局变量bar引用,处于一直可访问状态。通过执行bar()可以打印出hello closure!。如果想释放掉可以将bar = null即可。
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
实例------>demos/closures.html
<span style="font-size: 14px;"><!DOCTYPE html><br><html lang="en"><br><br><head><br> <meta charset="UTF-8"><br> <meta name="viewport" content="width=device-width, initial-scale=1.0"><br> <meta http-equiv="X-UA-Compatible" content="ie=edge"><br> <title>Closure</title><br></head><br><br><body><br> <p>不断单击【click】按钮</p><br> <button id="click_button">Click</button><br> <script><br> function f() {<br> var str = Array(10000).join('#');<br> var foo = {<br> name: 'foo'<br> }<br> function unused() {<br> var message = 'it is only a test message';<br> str = 'unused: ' + str;<br> }<br> function getData() {<br> return 'data';<br> }<br> return getData;<br> }<br><br> var list = [];<br> <br> document.querySelector('#click_button').addEventListener('click', function () {<br> list.push(f());<br> }, false);<br> </script><br></body><br><br></html><br></span>
这里结合Chrome的Devtools->Memory工具进行分析,操作步骤如下:
⚠️注:最好在隐藏窗口中进行分析工作,避免浏览器插件影响分析结果
选中【Record allocation timeline】选项
执行一次CG
单击【start】按钮开始记录堆分析
连续单击【click】按钮十多次
停止记录堆分析
上图中蓝色柱形条表示随着时间新分配的内存。选中其中某条蓝色柱形条,过滤出对应新分配的对象:
查看对象的详细信息:
从图可知,在返回的闭包作用链(Scopes)中携带有它所在函数的作用域,作用域中还包含一个str字段。而str字段并没有在返回getData()中使用过。为什么会存在在作用域中,按理应该被GC回收掉, why
原因是在相同作用域内创建的多个内部函数对象是共享同一个变量对象(variable object)。如果创建的内部函数没有被其他对象引用,不管内部函数是否引用外部函数的变量和函数,在外部函数执行完,对应变量对象便会被销毁。反之,如果内部函数中存在有对外部函数变量或函数的访问(可以不是被引用的内部函数),并且存在某个或多个内部函数被其他对象引用,那么就会形成闭包,外部函数的变量对象就会存在于闭包函数的作用域链中。这样确保了闭包函数有权访问外部函数的所有变量和函数。了解了问题产生的原因,便可以对症下药了。对代码做如下修改:
<span style="font-size: 14px;"> function f() {<br> var str = Array(10000).join('#');<br> var foo = {<br> name: 'foo'<br> }<br> function unused() {<br> var message = 'it is only a test message';<br> // str = 'unused: ' + str; //删除该条语句<br> }<br> function getData() {<br> return 'data';<br> }<br> return getData;<br> }<br><br> var list = [];<br> <br> document.querySelector('#click_button').addEventListener('click', function () {<br> list.push(f());<br> }, false);<br></span>
getData()和unused()内部函数共享f函数对应的变量对象,因为unused()内部函数访问了f作用域内str变量,所以str字段存在于f变量对象中。加上getData()内部函数被返回,被其他对象引用,形成了闭包,因此对应的f变量对象存在于闭包函数的作用域链中。这里只要将函数unused中str = 'unused: ' + str;语句删除便可解决问题。
查看一下闭包信息:
DOM泄露
在JavaScript中,DOM操作是非常耗时的。因为JavaScript/ECMAScript引擎独立于渲染引擎,而DOM是位于渲染引擎,相互访问需要消耗一定的资源。如Chrome浏览器中DOM位于WebCore,而JavaScript/ECMAScript位于V8中。假如将JavaScript/ECMAScript、DOM分别想象成两座孤岛,两岛之间通过一座收费桥连接,过桥需要交纳一定“过桥费”。JavaScript/ECMAScript每次访问DOM时,都需要交纳“过桥费”。因此访问DOM次数越多,费用越高,页面性能就会受到很大影响。了解更多ℹ️
为了减少DOM访问次数,一般情况下,当需要多次访问同一个DOM方法或属性时,会将DOM引用缓存到一个局部变量中。但如果在执行某些删除、更新操作后,可能会忘记释放掉代码中对应的DOM引用,这样会造成DOM内存泄露。
实例------>demos/dom.html
<span style="font-size: 14px;"><!DOCTYPE html><br><html lang="en"><br><head><br> <meta charset="UTF-8"><br> <meta name="viewport" content="width=device-width, initial-scale=1.0"><br> <meta http-equiv="X-UA-Compatible" content="ie=edge"><br> <title>Dom-Leakage</title><br></head><br><body><br> <input type="button" value="remove" class="remove"><br> <input type="button" value="add" class="add"><br><br> <p class="container"><br> <pre class="wrapper">

热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)

如何使用WebSocket和JavaScript实现在线语音识别系统引言:随着科技的不断发展,语音识别技术已经成为了人工智能领域的重要组成部分。而基于WebSocket和JavaScript实现的在线语音识别系统,具备了低延迟、实时性和跨平台的特点,成为了一种被广泛应用的解决方案。本文将介绍如何使用WebSocket和JavaScript来实现在线语音识别系

股票分析必备工具:学习PHP和JS绘制蜡烛图的步骤,需要具体代码示例随着互联网和科技的快速发展,股票交易已经成为许多投资者的重要途径之一。而股票分析是投资者决策的重要一环,其中蜡烛图被广泛应用于技术分析中。学习如何使用PHP和JS绘制蜡烛图将为投资者提供更多直观的信息,帮助他们更好地做出决策。蜡烛图是一种以蜡烛形状来展示股票价格的技术图表。它展示了股票价格的

人脸检测识别技术已经是一个比较成熟且应用广泛的技术。而目前最为广泛的互联网应用语言非JS莫属,在Web前端实现人脸检测识别相比后端的人脸识别有优势也有弱势。优势包括减少网络交互、实时识别,大大缩短了用户等待时间,提高了用户体验;弱势是:受到模型大小限制,其中准确率也有限。如何在web端使用js实现人脸检测呢?为了实现Web端人脸识别,需要熟悉相关的编程语言和技术,如JavaScript、HTML、CSS、WebRTC等。同时还需要掌握相关的计算机视觉和人工智能技术。值得注意的是,由于Web端的计

WebSocket与JavaScript:实现实时监控系统的关键技术引言:随着互联网技术的快速发展,实时监控系统在各个领域中得到了广泛的应用。而实现实时监控的关键技术之一就是WebSocket与JavaScript的结合使用。本文将介绍WebSocket与JavaScript在实时监控系统中的应用,并给出代码示例,详细解释其实现原理。一、WebSocket技

如何使用WebSocket和JavaScript实现在线预约系统在当今数字化的时代,越来越多的业务和服务都需要提供在线预约功能。而实现一个高效、实时的在线预约系统是至关重要的。本文将介绍如何使用WebSocket和JavaScript来实现一个在线预约系统,并提供具体的代码示例。一、什么是WebSocketWebSocket是一种在单个TCP连接上进行全双工

如何利用JavaScript和WebSocket实现实时在线点餐系统介绍:随着互联网的普及和技术的进步,越来越多的餐厅开始提供在线点餐服务。为了实现实时在线点餐系统,我们可以利用JavaScript和WebSocket技术。WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端与服务器的实时双向通信。在实时在线点餐系统中,当用户选择菜品并下单

随着互联网金融的迅速发展,股票投资已经成为了越来越多人的选择。而在股票交易中,蜡烛图是一种常用的技术分析方法,它能够显示股票价格的变化趋势,帮助投资者做出更加精准的决策。本文将通过介绍PHP和JS的开发技巧,带领读者了解如何绘制股票蜡烛图,并提供具体的代码示例。一、了解股票蜡烛图在介绍如何绘制股票蜡烛图之前,我们首先需要了解一下什么是蜡烛图。蜡烛图是由日本人

JavaScript和WebSocket:打造高效的实时天气预报系统引言:如今,天气预报的准确性对于日常生活以及决策制定具有重要意义。随着技术的发展,我们可以通过实时获取天气数据来提供更准确可靠的天气预报。在本文中,我们将学习如何使用JavaScript和WebSocket技术,来构建一个高效的实时天气预报系统。本文将通过具体的代码示例来展示实现的过程。We
