什么是内存泄露

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

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

1、意外的全局变量

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

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

function foo(arg) {

   bar = "this is a hidden global variable with a large of data";

}

  

等同于:

function foo(arg) {

   window.bar = "this is an explicit global variable with a large of data";

}

  

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

function foo() {

   this.variable = "potential accidental global";

}

// 当在全局作用域中调用foo函数,此时this指向的是全局对象(window),而不是'undefined'

foo();

  

解决方法:

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

function foo(arg) {

   "use strict" // 在foo函数作用域内开启严格模式

   bar = "this is an explicit global variable with a large of data";// 报错:因为bar还没有被声明

}

  

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

function foo(arg) {

   window.bar = "this is a explicit global variable with a large of data";

}

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

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

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

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

2、console.log

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

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

实例------>demos/log.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Leaker</title>
</head>
<body>
<input type="button" value="click">
<script> !function () {
function Leaker() {
this.init();
}; Leaker.prototype = { init: function () {
this.name = (Array(100000)).join('*');
console.log("Leaking an object %o: %o", (new Date()), this);// this对象不能被回收
},
destroy: function () { // do something.... }
};
document.querySelector('input').addEventListener('click', function () {
new Leaker();
}, false);
}() </script> </body>
</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作用域内的变量,这就是一个闭包——————本质上闭包是将函数内部和外部连接起来的一座桥梁。

function foo(message) {
function closure() {
console.log(message)
};
return closure;
} // 使用
var bar = foo("hello closure!");
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内存泄露。

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

5、定时器  

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

setTimeout(function() {
// do something. . . .
setTimeout(arguments.callee, interval);
}, interval);

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

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

6、EventListener

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

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

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

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

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

最新文章

  1. [BI项目记]-搭建代码管理环境之服务端
  2. HBase的Write Ahead Log (WAL) —— API与基本概念
  3. [解决方案] pythonchallenge level 1
  4. struts2是如何加载相关的package元素节点信息的
  5. noi题库(noi.openjudge.cn) 1.8编程基础之多维数组T01——T10
  6. HBase的二级索引,以及phoenix的安装(需再做一次)
  7. 268. Missing Number
  8. UVa 11859 (Nim) Division Game
  9. ant 具体命令行展示代码
  10. Servlet3.1规范和JSP2.3规范
  11. Linux下批量杀掉 包含某个关键字的 程序进程
  12. Linux恢复误删除的文件或者目录(转)
  13. HTTP协议请求头信息和响应头信息
  14. navicat连接mysql时出现2003(10060)错误
  15. Keras模型的导出和pb文件的转换
  16. AngularJS之登录显示用户名
  17. 使用Axure RP原型设计实践03,制作一个登录界面的原型
  18. hdoj1180 诡异的楼梯(bfs+奇偶判断)
  19. 使用JDBC connect获取数据库表结构信息
  20. 一些基于jQuery开发的控件

热门文章

  1. [BZOJ4103][Thu Summer Camp 2015]异或运算 可持久化Trie树
  2. [洛谷P5137]polynomial
  3. [洛谷P1642]规划
  4. 最长上升子序列nlogn算法
  5. Ciesz się Polską
  6. 【OpenCV】SIFT原理与源码分析:关键点描述
  7. 徒手创建一个 jsp 项目
  8. 前端PHP入门-032-异常处理-应用级别
  9. uva 1639 Candy (对数处理精度)
  10. Redis实战(五)CentOS 7上搭建Redis集群