Home >Web Front-end >JS Tutorial >Common JavaScript memory leaks
What is a memory leak
Memory leak refers to a program that fails to be released due to negligence or error and is no longer used. of memory. Memory leaks do not refer to the physical disappearance of memory, but rather to the fact that after the application allocates a certain segment of memory, due to design errors, it loses control of the segment of memory before it is released, thus causing a waste of memory.
Memory leaks can usually only be analyzed by programmers who have access to the program source code. However, quite a few people are accustomed to describing any unwanted increase in memory usage as a memory leak, even though this is not strictly accurate.
————wikipedia
How JavaScript handles undeclared variables: in the global Create a reference to the variable on the object (that is, a property on the global object, not a variable, because it can be deleted through delete). If you are in a browser, the global object is the window object.
If undeclared variables cache a large amount of data, the data can only be released when the window is closed or the page is refreshed. This can cause unexpected memory leaks.
<span style="font-size: 14px;">function foo(arg) {<br> bar = "this is a hidden global variable with a large of data";<br>}<br></span>
Equivalent to:
<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>
Additionally, unexpected global variables are created via 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>
Add 'use strict' to the JavaScript file and turn on strict mode, which can effectively avoid the above problems.
<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>
If you need to use global variables in a function, you can explicitly declare it on the window as shown in the following code:
<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>
This way Not only is it highly readable, but it is also convenient for later maintenance
When it comes to global variables, you need to pay attention to those global variables used to temporarily store large amounts of data to ensure that these data are processed after Then set it to null or reassign it. Global variables are also commonly used as cache. Generally, cache is used for performance optimization. For performance, it is best to set an upper limit on the size of the cache. Because the cache cannot be reclaimed, a higher cache will result in higher memory consumption.
##console.log: Prints a message to the web development console, often used for debugging and analysis during development . Sometimes during development, you need to print some object information, but you forget to remove the console.log statement when publishing, which may cause memory leaks.
The object passed to console.log cannot be garbage collected ♻️ because the object information needs to be viewed in the development tool after the code is run. So it is best not to console.log any objects in the production environment.
<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>
Here is combined with Chrome’s Devtools–>Performance to do some Analysis, the steps are as follows:
⚠️Note: It is best to perform the analysis in a hidden window to avoid browser plug-ins from affecting the analysis results
Open the record of [Performance] item
Execute CG once and create a baseline reference line
Click the [click] button three times in succession to create three new Leaker objects
Execute CG once
Stop recording
<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>
From the comparative analysis results, we can see that the objects printed by console.log will not be garbage collected. Recycled. Therefore, it is best not to console.log any large objects in the page, as this may affect the overall performance of the page, especially in a production environment. In addition to console.log, there are also similar problems such as console.dir, console.error, console.warn, etc. These details require special attention.
当一个函数A返回一个内联函数B,即使函数A执行完,函数B也能访问函数A作用域内的变量,这就是一个闭包——————本质上闭包是将函数内部和外部连接起来的一座桥梁。 在函数foo内创建的函数closure对象是不能被回收掉的,因为它被全局变量bar引用,处于一直可访问状态。通过执行bar()可以打印出hello closure!。如果想释放掉可以将bar = null即可。 由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。 这里结合Chrome的Devtools->Memory工具进行分析,操作步骤如下: ⚠️注:最好在隐藏窗口中进行分析工作,避免浏览器插件影响分析结果 选中【Record allocation timeline】选项 执行一次CG 单击【start】按钮开始记录堆分析 连续单击【click】按钮十多次 停止记录堆分析 上图中蓝色柱形条表示随着时间新分配的内存。选中其中某条蓝色柱形条,过滤出对应新分配的对象: 查看对象的详细信息: 从图可知,在返回的闭包作用链(Scopes)中携带有它所在函数的作用域,作用域中还包含一个str字段。而str字段并没有在返回getData()中使用过。为什么会存在在作用域中,按理应该被GC回收掉, why 原因是在相同作用域内创建的多个内部函数对象是共享同一个变量对象(variable object)。如果创建的内部函数没有被其他对象引用,不管内部函数是否引用外部函数的变量和函数,在外部函数执行完,对应变量对象便会被销毁。反之,如果内部函数中存在有对外部函数变量或函数的访问(可以不是被引用的内部函数),并且存在某个或多个内部函数被其他对象引用,那么就会形成闭包,外部函数的变量对象就会存在于闭包函数的作用域链中。这样确保了闭包函数有权访问外部函数的所有变量和函数。了解了问题产生的原因,便可以对症下药了。对代码做如下修改: getData()和unused()内部函数共享f函数对应的变量对象,因为unused()内部函数访问了f作用域内str变量,所以str字段存在于f变量对象中。加上getData()内部函数被返回,被其他对象引用,形成了闭包,因此对应的f变量对象存在于闭包函数的作用域链中。这里只要将函数unused中str = 'unused: ' + str;语句删除便可解决问题。 查看一下闭包信息: 在JavaScript中,DOM操作是非常耗时的。因为JavaScript/ECMAScript引擎独立于渲染引擎,而DOM是位于渲染引擎,相互访问需要消耗一定的资源。如Chrome浏览器中DOM位于WebCore,而JavaScript/ECMAScript位于V8中。假如将JavaScript/ECMAScript、DOM分别想象成两座孤岛,两岛之间通过一座收费桥连接,过桥需要交纳一定“过桥费”。JavaScript/ECMAScript每次访问DOM时,都需要交纳“过桥费”。因此访问DOM次数越多,费用越高,页面性能就会受到很大影响。了解更多ℹ️ 为了减少DOM访问次数,一般情况下,当需要多次访问同一个DOM方法或属性时,会将DOM引用缓存到一个局部变量中。但如果在执行某些删除、更新操作后,可能会忘记释放掉代码中对应的DOM引用,这样会造成DOM内存泄露。closures(闭包)
<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>
实例------>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>
<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>
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">