造成JavaScript内存泄漏的几种原因分析

何为内存泄漏:由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

JavaScript造成内存泄漏的几种原因分析:


1、意外的全局变量
js对未声明变量会在全局最高对象上创建它的引用,(是以属性存在的,而不是变量),如果在游览器上就是window对象,如果在node环境下就是global;如果未声明的变量缓存大量的数据,它可能只有在页面被刷新或者被关闭的时候才会释放内存,这样就造成了内存意外泄漏。如下例子:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>BFW NEW PAGE</title>
    <script type="text/javascript">
        function testfunc() {
            bfwname = "线上技术学习开发平台"
        }
        testfunc()
        console.log(window)
    </script>
</head>
<body>

</body>
</html>


在控制台可以看到
造成JavaScript内存泄漏的几种原因分析
还有通过this创建意外的全局变量

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>BFW NEW PAGE</title>
    <script type="text/javascript">
        function testfunc() {
            this.bfwname = "线上技术学习开发平台"
        }
        testfunc()
        console.log(window.bfwname)
    </script>
</head>
<body>

</body>
</html>



造成JavaScript内存泄漏的几种原因分析
针对上面类型的内存泄漏我们在平时一定要声明变量,不要有全局直接引用。(在JavaScript文件中添加 'use strict',开启严格模式,可以有效地避免上述问题。)

2、console.log
作为前端平时使用console.log在控制台打出相对应的信息可以说是非常常见。但如果没有去掉console.log可能会存在内存泄漏。因为在代码运行之后需要在开发工具能查看对象信息,所以传递给console.log的对象是不能被垃圾回收。

3、闭包
首先闭包是一个函数A返回一个内联的函数B,及时A函数执行完函数B也可以访问函数A里面的变量,这就是一个简单的闭包。本质上闭包是将函数内部和外部连接起来的一座桥梁。
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>BFW NEW PAGE</title>
    <script type="text/javascript">
        function Bfw(name) {
            function sendName() {
                console.log(name)
            }
            return sendName
        }
        var test = Bfw("线上技术学习开发平台")
        test() //tokey
    </script>
</head>
<body>

</body>
</html>


在Bfw()内部创建的sendName()函数是不会被回收的,因为它被全局变量test引用,处于随时被调用的状态。如果向释放内存可以设置test=null;由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

4、DOM泄漏
游览器中DOM和js采用的是不一样的引擎,DOM采用的是渲染引擎,而js采用的是v8引擎,所以在用js操作DOM时会比较耗费性能,因为他们需要桥来链接他们。为了减少DOM的操作,我们一般将常用的DOM。我们会采用变量引用的方式会将其缓存在当前环境。如果在进行一些删除、更新操作之后,可能会忘记释放已经缓存的DOM,话不多说直接来个例子

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>BFW NEW PAGE</title>
    <script id="bfwone" data="dep=jquery.17&err=0" type="text/javascript" src="http://repo.bfw.wiki/bfwrepo/js/bfwone.js"></script>
    <script type="text/javascript">
        bready(function() {
            var add = document.querySelector("#add");
            var remove = document.querySelector("#remove");
            var main = document.querySelector(".main")
            var test = document.querySelector(".test")
            add.onclick = function () {
                var itemN = document.createElement('div')
                var txt = document.createTextNode('上')
                itemN.appendChild(txt)
                main.appendChild(itemN)
            }
            remove.onclick = function () {
                main.removeChild(test)
            }
        });
    </script>
</head>
<body>
    <div class="main">
        <div class="test">
            天
        </div>
        <div class="item">
            天
        </div>
        <div class="item">
            向
        </div>
    </div>
    <button id="add">点击我增加</button>
    <button id="remove">点击我减少</button>

</body>
</html>


通过chrome游览器的内存调试工作我们可以看到
造成JavaScript内存泄漏的几种原因分析

在我点击了三次增加之后,可以明显的看到节点(绿线)有三次明显的增加,之后我又删除了一个节点,但绿线没有下降,这是为什么呢?,这也就是内存泄漏。原因就是删除的DOM在js中有全局的引用。也就是我删除的test在文中被引用,所以无法释放内存。所以在删除更新等操作后应该将其设置为null。

造成JavaScript内存泄漏的几种原因分析

5、被遗忘的timers
js中常用的定时器setInterval()、setTimeout().他们都是规定延迟一定的时间执行某个代码,而其中setInterval()和链式setTimeout()在使用完之后如果没有手动关闭,会一直存在执行占用内存,所以在不用的时候我们可以通过clearInterval()、clearTimeout() 来关闭其对应的定时器,释放内存。熟悉朋友都知道这类定时器是有误差的,所以游览器给出了专门的API-requestAnimationFrame();大家可以试一下。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>BFW NEW PAGE</title>
    <script id="bfwone" data="dep=jquery.17&err=0" type="text/javascript" src="http://repo.bfw.wiki/bfwrepo/js/bfwone.js"></script>
    <script type="text/javascript">
        bready(function() {

            var serverData = loadData();
            setInterval(function() {
                var renderer = document.getElementById('renderer');
                if (renderer) {
                    renderer.innerHTML = JSON.stringify(serverData);
                }
            }, 5000); //This will be executed every ~5 seconds.
        });
    </script>
</head>
<body>
    <div id="renderer">

    </div>


</body>
</html>

这个例子阐述着 timers 可能发生的情况:计时器会引用不再需要的节点或数据。

renderer 可能在将来会被移除,使得 interval 内的整个块都不再被需要。但是,interval handler 因为 interval 的存活,所以无法被回收(需要停止 interval,才能回收)。如果 interval handler 无法被回收,则它的依赖也不能被回收。这意味着 serverData——可能存储了大量数据,也不能被回收。在观察者模式下,重要的是在他们不再被需要的时候显式地去删除它们(或者让相关对象变为不可达)。


{{collectdata}}

网友评论0