软件研发

【专家视点】4 种常见的 Javascript 内存泄露

2017-01-19 16:25:48 | 来源:中培企业IT培训网

Javascript 是Java家族中最受欢迎的成员,在该语言的开发应用过程中,其内存泄漏是一个重要的问题。中培伟业《企业级JAVA高级开发技术实战》培训专家刘老师在这里介绍了详解4 种常见的 Javascript 内存泄露问题。

1: 意外的全局变量

Javascript 语言的设计目标之一是开发一种类似于 Java 但是对初学者十分友好的语言。体现 JavaScript 宽容性的一点表现在它处理未声明变量的方式上:一个未声明变量的引用会在全局对象中创建一个新的变量。

如果 bar 是一个应该指向 foo 函数作用域内变量的引用,但是你忘记使用 var 来声明这个变量,这时一个全局变量就会被创建出来。在这个例子中,一个简单的字符串泄露并不会造成很大的危害,但这无疑是错误的。

为了防止这种错误的发生,可以在你的 JavaScript 文件开头添加 'use strict'; 语句。这个语句实际上开启了解释 JavaScript 代码的严格模式,这种模式可以避免创建意外的全局变量。

全局变量的注意事项

尽管我们在讨论那些隐蔽的全局变量,但是也有很多代码被明确的全局变量污染的情况。按照定义来讲,这些都是不会被回收的变量(除非设置 null 或者被重新赋值)。特别需要注意的是那些被用来临时存储和处理一些大量的信息的全局变量。如果你必须使用全局变量来存储很多的数据,请确保在使用过后将它设置为 null 或者将它重新赋值。

常见的和全局变量相关的引发内存消耗增长的原因就是缓存。缓存存储着可复用的数据。为了让这种做法更高效,必须为缓存的容量规定一个上界。由于缓存不能被及时回收的缘故,缓存无限制地增长会导致很高的内存消耗。

2: 被遗漏的定时器和回调函数

JavaScript 中 setInterval 的使用十分常见。其他的库也经常会提供观察者和其他需要回调的功能。这些库中的绝大部分都会关注一点,就是当它们本身的实例被销毁之前销毁所有指向回调的引用。

那些表示节点的对象在将来可能会被移除掉,所以将整个代码块放在周期处理函数中并不是必要的。然而,由于周期函数一直在运行,处理函数并不会被回收(只有周期函数停止运行之后才开始回收内存)。如果周期处理函数不能被回收,它的依赖程序也同样无法被回收。这意味着一些资源,也许是一些相当大的数据都也无法被回收。

以前在 IE 浏览器的垃圾回收器上会导致一个 bug(或者说是浏览器设计上的问题)。旧版本的 IE 浏览器不会发现 DOM 节点和 JavaScript 代码之间的循环引用。这是一种观察者的典型情况,观察者通常保留着一个被观察者的引用换句话说,在 IE 浏览器中,每当一个观察者被添加到一个节点上时,就会发生一次内存泄漏。这也就是开发者在节点或者空的引用被添加到观察者中之前显式移除处理方法的原因。

目前,现代的浏览器(包括 IE 和 Microsoft Edge)都使用了可以发现这些循环引用并正确的处理它们的现代化垃圾回收算法。换言之,严格地讲,在废弃一个节点之前调用 removeEventListener 不再是必要的操作。

像是 jQuery 这样的框架和库(当使用一些特定的 API 时候)都在废弃一个结点之前移除了 listener 。它们在内部就已经处理了这些事情,并且保证不会产生内存泄露,即便程序运行在那些问题很多的浏览器中,比如老版本的 IE。

3: DOM 之外的引用

有些情况下将 DOM 结点存储到数据结构中会十分有用。假设你想要快速地更新一个表格中的几行,如果你把每一行的引用都存储在一个字典或者数组里面会起到很大作用。如果你这么做了,程序中将会保留同一个结点的两个引用:一个引用存在于 DOM 树中,另一个被保留在字典中。如果在未来的某个时刻你决定要将这些行移除,则需要将所有的引用清除。

假设你在 JavaScript 代码中保留了一个表格中特定单元格(一个 <td> 标签)的引用。在将来你决定将这个表格从 DOM 中移除,但是仍旧保留这个单元格的引用。凭直觉,你可能会认为 GC 会回收除了这个单元格之外所有的东西,但是实际上这并不会发生:单元格是表格的一个子节点且所有子节点都保留着它们父节点的引用。换句话说,JavaScript 代码中对单元格的引用导致整个表格被保留在内存中。所以当你想要保留 DOM 元素的引用时,要仔细的考虑清除这一点。

4: 闭包

JavaScript 开发中一个重要的内容就是闭包,它是可以获取父级作用域的匿名函数。Meteor 的开发者发现在一种特殊情况下有可能会以一种很微妙的方式产生内存泄漏,这取决于 JavaScript 运行时的实现细节。

本质上来讲,创建了一个闭包链表(根节点是 theThing 形式的变量),而且每个闭包作用域都持有一个对大数组的间接引用,这导致了一个巨大的内存泄露。

标签: Java开发