什么是内存泄露

指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,

而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

1、意外的全局变量

JavaScript对未声明变量的处理方式:在全局对象上创建该变量的引用(即全局对象上的属性,不是变量,因为它能通过 delete删除)。如果在浏览器中,全局对象就是window对象。

如果未声明的变量缓存大量的数据,会导致这些数据只有在窗口关闭或重新刷新页面时才能被释放。这样会造成意外的内存泄漏。

  1. function foo(arg) {
  2.  
  3. bar = "this is a hidden global variable with a large of data";
  4.  
  5. }

  

等同于:

  1. function foo(arg) {
  2.  
  3. window.bar = "this is an explicit global variable with a large of data";
  4.  
  5. }

  

另外,通过this创建意外的全局变量:

  1. function foo() {
  2.  
  3. this.variable = "potential accidental global";
  4.  
  5. }
  6.  
  7. // 当在全局作用域中调用foo函数,此时this指向的是全局对象(window),而不是'undefined'
  8.  
  9. foo();

  

解决方法:

在JavaScript文件中添加 'use strict',开启严格模式,可以有效地避免上述问题。

  1. function foo(arg) {
  2.  
  3. "use strict" // 在foo函数作用域内开启严格模式
  4.  
  5. bar = "this is an explicit global variable with a large of data";// 报错:因为bar还没有被声明
  6.  
  7. }

  

如果需要在一个函数中使用全局变量,可以像如下代码所示,在window上明确声明:

  1. function foo(arg) {
  2.  
  3. window.bar = "this is a explicit global variable with a large of data";
  4.  
  5. }

这样不仅可读性高,而且后期维护也方便

谈到全局变量,需要注意那些用来临时存储大量数据的全局变量,确保在处理完这些数据后将其设置为null或重新赋值。

全局变量也常用来做cache,一般cache都是为了性能优化才用到的,为了性能,最好对cache的大小做个上限限制。

因为cache是不能被回收的,越高cache会导致越高的内存消耗。

2、console.log

console.log:向web开发控制台打印一条消息,常用来在开发时调试分析。有时在开发时,需要打印一些对象信息,但发布时却忘记去掉 console.log语句,这可能造成内存泄露。

在传递给 console.log的对象是不能被垃圾回收 ♻️,因为在代码运行之后需要在开发工具能查看对象信息。所以最好不要在生产环境中 console.log任何对象。

实例------>demos/log.html
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Leaker</title>
  8. </head>
  9. <body>
  10. <input type="button" value="click">
  11. <script>
  12.  
  13. !function () {
  14. function Leaker() {
  15. this.init();
  16. };
  17.  
  18. Leaker.prototype = {
  19.  
  20. init: function () {
  21. this.name = (Array(100000)).join('*');
  22. console.log("Leaking an object %o: %o", (new Date()), this);// this对象不能被回收
  23. },
  24. destroy: function () {
  25.  
  26. // do something....
  27.  
  28. }
  29. };
  30. document.querySelector('input').addEventListener('click', function () {
  31. new Leaker();
  32. }, false);
  33. }()
  34.  
  35. </script>
  36.  
  37. </body>
  38. </html>

这里结合Chrome的Devtools–>Performance做一些分析,操作步骤如下:
   开启【Performance】项的记录
   执行一次CG,创建基准参考线
   连续单击【click】按钮三次,新建三个Leaker对象
   执行一次CG
   停止记录

去掉console.log("Leaking an object %o: %o",(newDate()),this);语句。重复上述的操作步骤  

从对比分析结果可知, console.log打印的对象是不会被垃圾回收器回收的。因此最好不要在页面中 console.log任何大对象,这样可能会影响页面的整体性能,特别在生产环境中。

除了 console.log外,另外还有 console.dirconsole.errorconsole.warn等都存在类似的问题,这些细节需要特别的关注。

3、closures(闭包)

当一个函数A返回一个内联函数B,即使函数A执行完,函数B也能访问函数A作用域内的变量,这就是一个闭包——————本质上闭包是将函数内部和外部连接起来的一座桥梁。

  1. function foo(message) {
  2. function closure() {
  3. console.log(message)
  4. };
  5. return closure;
  6. }
  7.  
  8. // 使用
  9. var bar = foo("hello closure!");
  10. bar()// 返回 'hello closure!'

  

在函数foo内创建的函数closure对象是不能被回收掉的,因为它被全局变量bar引用,处于一直可访问状态。通过执行 bar()可以打印出 hello closure!。如果想释放掉可以将 bar=null即可。

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

4、DOM泄露

在JavaScript中,DOM操作是非常耗时的。因为JavaScript/ECMAScript引擎独立于渲染引擎,而DOM是位于渲染引擎,相互访问需要消耗一定的资源。如Chrome浏览器中DOM位于WebCore,而JavaScript/ECMAScript位于V8中。

假如将JavaScript/ECMAScript、DOM分别想象成两座孤岛,两岛之间通过一座收费桥连接,过桥需要交纳一定“过桥费”。JavaScript/ECMAScript每次访问DOM时,都需要交纳“过桥费”。

因此访问DOM次数越多,费用越高,页面性能就会受到很大影响。了解更多ℹ️

为了减少DOM访问次数,一般情况下,当需要多次访问同一个DOM方法或属性时,会将DOM引用缓存到一个局部变量中。

但如果在执行某些删除、更新操作后,可能会忘记释放掉代码中对应的DOM引用,这样会造成DOM内存泄露。

  1. <script>
  2. var refA = document.getElementById('refA');
  3. var refB = document.getElementById('refB');
  4. document.body.removeChild(refA);
  5.  
  6. // #refA不能GC回收,因为存在变量refA对它的引用。将其对#refA引用释放,但还是无法回收#refA。
  7. refA = null
  8.  
  9. // 还存在变量refB对#refA的间接引用(refB引用了#refB,而#refB属于#refA)。将变量refB对#refB的引用释放,#refA就可以被GC回收。
  10. refB = null
  11. </script>

5、定时器  

在JavaScript常用 setInterval()来实现一些动画效果。当然也可以使用链式 setTimeout()调用模式来实现:

  1. setTimeout(function() {
  2. // do something. . . .
  3. setTimeout(arguments.callee, interval);
  4. }, interval);

如果在不需要 setInterval()时,没有通过 clearInterval()方法移除,那么 setInterval()会不停地调用函数,直到调用 clearInterval()或窗口关闭。

如果链式 setTimeout()调用模式没有给出终止逻辑,也会一直运行下去。因此再不需要重复定时器时,确保对定时器进行清除,避免占用系统资源。 

6、EventListener

做移动开发时,需要对不同设备尺寸做适配。如在开发组件时,有时需要考虑处理横竖屏适配问题。一般做法,在横竖屏发生变化时,需要将组件销毁后再重新生成。

而在组件中会对其进行相关事件绑定,如果在销毁组件时,没有将组件的事件解绑,在横竖屏发生变化时,就会不断地对组件进行事件绑定。这样会导致一些异常,甚至可能会导致页面崩掉。

同一个元素节点注册了多个相同的EventListener,那么重复的实例会被抛弃。

这么做不会让得EventListener被重复调用,也不需要用removeEventListener手动清除多余的EventListener,因为重复的都被自动抛弃了。

而这条规则只是针对于命名函数。对于匿名函数,浏览器会将其看做不同的EventListener,所以只要将匿名的EventListener,命名一下就可以解决问题:

常见的 JavaScript 内存泄露的更多相关文章

  1. [转]常见的JavaScript内存泄露

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

  2. 常见的几种JavaScript内存泄露

    总结一下常见的几种JavaScript内存泄露: 1.意外的全局变量 全局变量属于window对象,所以只会随着window销毁才会销毁. 2.console.log() conaole.log()函 ...

  3. 4类 JavaScript 内存泄露及如何避免

    原文:4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them笔记:涂鸦码龙 译者注:本文并没有逐字逐句的翻译,而是把我认为重要 ...

  4. JavaScript内存泄露,闭包内存泄露如何解决

    本文原链接:https://cloud.tencent.com/developer/article/1340979 JavaScript 内存泄露的4种方式及如何避免 简介 什么是内存泄露? Java ...

  5. [ Javascript ] 内存泄露以及循环引用解析

    内存泄露 在javascript中,我们非常少去关注内存的管理. 我们创建变量,使用变量,浏览器关注这些底层的细节都显得非常正常. 可是当应用程序变得越来越复杂而且ajax化之后,或者用户在一个页面停 ...

  6. Javascript内存泄露

    在过去一些的时候,Web开发人员并没有太多的去关注内存泄露问题.那时的页面间联系大都比较简单,并主要使用不同的连接地址在同一个站点中导航,这样的设计方式是非常有利于浏览器释放资源的.即使Web页面运行 ...

  7. 【翻译】JavaScript内存泄露

    原文地址:http://javascript.info/tutorial/memory-leaks#tools 我们在进行JavaScript开发时,很少会考虑内存的管理.JavaScript中变量的 ...

  8. JavaScript 内存泄露以及如何处理

    一.前言 一直有打算总结一下JS内存泄露的方面的知识的想法,但是总是懒得提笔. 富兰克林曾经说过:懒惰,像生鏽一样,比操劳更能消耗身体,经常用的钥匙总是亮闪闪的.安利一下,先起个头. 二.内存声明周期 ...

  9. 容易造成JavaScript内存泄露几个方面

    高效的JavaScript Web应用必须流畅,快速.与用户交互的任何应用程序,都需要考虑如何确保内存有效使用,因为如果消耗过多,页面就会崩溃,迫使用户重新加载.而你只能躲在角落哭泣. 自动垃圾收集是 ...

随机推荐

  1. [您有新的未分配科技点]数位DP:从板子到基础(例题 bzoj1026 windy数 bzoj3131 淘金)

    只会统计数位个数或者某种”符合简单规律”的数并不够……我们需要更多的套路和应用 数位dp中常用的思想是“分类讨论”思想.下面我们就看一道典型的分类讨论例题 1026: [SCOI2009]windy数 ...

  2. QML从文件加载组件简单示例

    QML从文件加载组件简单示例 文件目录列表: Project1.pro QT += quick CONFIG += c++ CONFIG += declarative_debug CONFIG += ...

  3. [POI2012]OKR-A Horrible Poem hash

    题面:洛谷 题解: 首先我们需要知道一个性质,串s的最小循环节 = len - next[len].其中next[len]表示串s的一个最长长度使得s[1] ~ s[next[len]] == s[l ...

  4. 测试开发linux面试之三:后台进程之操作

    Hi,大家好我是Tom,继上次分享之后这次给大家带来新的知识. 进程是Linux系统中一个非常重要的概念.Linux是一个多任务的操作系统,系统上经常同时运行着多个进程.我们不关心这些进程究竟是如何分 ...

  5. Implement Queue by Two Stacks

    As the title described, you should only use two stacks to implement a queue's actions. The queue sho ...

  6. 「CodePlus 2017 11 月赛」可做题

    这种题先二进制拆位,显然改的位置只有每一段确定的数的开头和结尾,只需要对于每一个可决策位置都尝试一下填1和0,然后取min即可. #include<iostream> #include&l ...

  7. 【DP】【CF855C】 Helga Hufflepuff's Cup

    Description 给你一个树,可以染 \(m\) 个颜色,定义一个特殊颜色 \(k\) , 要求保证整棵树上特殊颜色的个数不超过 \(x\) 个.同时,如果一个节点是特殊颜色,那么它的相邻节点的 ...

  8. UESTC--1682

    原题链接: 分析:求最小周期的应用. #include<cstdio> #include<cstring> #include<cmath> #include< ...

  9. 再谈System.arraycopy和Arrays.copyOf

    之前转载过一篇博文,介绍过这两个方法,今天想要再次详细的了解一下. public static native void arraycopy(Object src, int srcPos, Object ...

  10. Windows下的包管理器Chocolatey

    参考文档: https://www.jianshu.com/p/831aa4a280e7 https://www.jianshu.com/p/abaa0e8c261f