权威JavaScript 中的内存泄露模式


Posted in Javascript onAugust 13, 2007

作者:
Abhijeet Bhattacharya
(abhbhatt@in.ibm.com), 系统软件工程师, IBM India
Kiran Shivarama Shivarama Sundar (kisundar@in.ibm.com), 系统软件工程师, IBM India

2007 年 5 月 28 日

如果您知道内存泄漏的起因,那么在 JavaScript 中进行相应的防范就应该相当容易。在这篇文章中,作者 Kiran Sundar 和 Abhijeet Bhattacharya 将带您亲历 JavaScript 中的循环引用的全部基本知识,向您介绍为何它们会在某些浏览器中产生问题,尤其是在结合了闭包的情况下。在了解了您应该引起注意的常见内存泄漏模式之后,您还将学到应对这些泄漏的诸多方法。

JavaScript 是用来向 Web 页面添加动态内容的一种功能强大的脚本语言。它尤其特别有助于一些日常任务,比如验证密码和创建动态菜单组件。JavaScript 易学易用,但却很容易在某些浏览器中引起内存的泄漏。在这个介绍性的文章中,我们解释了 JavaScript 中的泄漏由何引起,展示了常见的内存泄漏模式,并介绍了如何应对它们。

注意本文假设您已经非常熟悉使用 JavaScript 和 DOM 元素来开发 Web 应用程序。本文尤其适合使用 JavaScript 进行 Web 应用程序开发的开发人员,也可供有兴趣创建 Web 应用程序的客户提供浏览器支持以及负责浏览器故障排除的人员参考。

JavaScript 中的内存泄漏

JavaScript 是一种垃圾收集式语言,这就是说,内存是根据对象的创建分配给该对象的,并会在没有对该对象的引用时由浏览器收回。JavaScript 的垃圾收集机制本身并没有问题,但浏览器在为 DOM 对象分配和恢复内存的方式上却有些出入。

Internet Explorer 和 Mozilla Firefox 均使用引用计数来为 DOM 对象处理内存。在引用计数系统,每个所引用的对象都会保留一个计数,以获悉有多少对象正在引用它。如果计数为零,该对象就会被销毁,其占用的内存也会返回给堆。虽然这种解决方案总的来说还算有效,但在循环引用方面却存在一些盲点。

循环引用的问题何在?

当两个对象互相引用时,就构成了循环引用,其中每个对象的引用计数值都被赋 1。在纯垃圾收集系统中,循环引用问题不大:若涉及到的两个对象中的一个对象被任何其他对象引用,那么这两个对象都将被垃圾收集。而在引用计数系统,这两个对象都不能被销毁,原因是引用计数永远不能为零。在同时使用了垃圾收集和引用计数的混合系统中,将会发生泄漏,因为系统不能正确识别循环引用。在这种情况下,DOM 对象和 JavaScript 对象均不能被销毁。清单 1 显示了在 JavaScript 对象和 DOM 对象间存在的一个循环引用。

清单 1. 循环引用导致了内存泄漏

Div Element

如上述清单中所示,JavaScript 对象 obj 拥有到 DOM 对象的引用,表示为 DivElement。而 DOM 对象则有到此 JavaScript 对象的引用,由 expandoProperty 表示。可见,JavaScript 对象和 DOM 对象间就产生了一个循环引用。由于 DOM 对象是通过引用计数管理的,所以两个对象将都不能销毁。

另一种内存泄漏模式

在清单 2 中,通过调用外部函数 myFunction 创建循环引用。同样,JavaScript 对象和 DOM 对象间的循环引用也会导致内存泄漏。

清单 2. 由外部函数调用引起的内存泄漏


正如这两个代码示例所示,循环引用很容易创建。在 JavaScript 最为方便的编程结构之一:闭包中,循环引用尤其突出。

JavaScript 中的闭包

JavaScript 的过人之处在于它允许函数嵌套。一个嵌套的内部函数可以继承外部函数的参数和变量,并由该外部函数私有。清单 3 显示了内部函数的一个示例。

清单 3. 一个内部函数

function parentFunction(paramA) { var a = paramA; function childFunction() { return a + 2; } return childFunction(); }

JavaScript 开发人员使用内部函数来在其他函数中集成小型的实用函数。如清单 3 所示,此内部函数 childFunction 可以访问外部函数 parentFunction 的变量。当内部函数获得和使用其外部函数的变量时,就称其为一个闭包

了解闭包

考虑如清单 4 所示的代码片段。

清单 4. 一个简单的闭包


在上述清单中,closureDemoInnerFunction 是在父函数 closureDemoParentFunction 中定义的内部函数。当用外部的 xclosureDemoParentFunction 进行调用时,外部函数变量 a 就会被赋值为外部的 x。函数会返回指向内部函数 closureDemoInnerFunction 的指针,该指针包括在变量 x 内。

外部函数 closureDemoParentFunction 的本地变量 a 即使在外部函数返回时仍会存在。这一点不同于 C/C++ 这样的编程语言,在 C/C++ 中,一旦函数返回,本地变量也将不复存在。在 JavaScript 中,在调用 closureDemoParentFunction 的时候,带有属性 a 的范围对象将会被创建。该属性包括值 paramA,又称为“外部 x”。同样地,当 closureDemoParentFunction 返回时,它将会返回内部函数 closureDemoInnerFunction,该函数包括在变量 x 中。

由于内部函数持有到外部函数的变量的引用,所以这个带属性 a 的范围对象将不会被垃圾收集。当对具有参数值 inner xx 进行调用时,即 x("inner x"),将会弹出警告消息,表明 “outer x innerx”。

清单 4 简要解释了 JavaScript 闭包。闭包功能非常强大,原因是它们使内部函数在外部函数返回时也仍然可以保留对此外部函数的变量的访问。不幸的是,闭包非常易于隐藏 JavaScript 对象 和 DOM 对象间的循环引用。

闭包和循环引用

在清单 5 中,可以看到一个闭包,在此闭包内,JavaScript 对象(obj)包含到 DOM 对象的引用(通过 id "element" 被引用)。而 DOM 元素则拥有到 JavaScript obj 的引用。这样建立起来的 JavaScript 对象和 DOM 对象间的循环引用将会导致内存泄漏。

清单 5. 由事件处理引起的内存泄漏模式


避免内存泄漏

幸好,JavaScript 中的内存泄漏是可以避免的。当确定了可导致循环引用的模式之后,正如我们在上述章节中所做的那样,您就可以开始着手应对这些模式了。这里,我们将以上述的 由事件处理引起的内存泄漏模式 为例来展示三种应对已知内存泄漏的方式。

一种应对 清单 5 中的内存泄漏的解决方案是让此 JavaScript 对象 obj 为空,这会显式地打破此循环引用,如清单 6 所示。

 清单 6. 打破循环引用


清单 7 是通过添加另一个闭包来避免 JavaScript 对象和 DOM 对象间的循环引用。

清单 7. 添加另一个闭包


清单 8 则通过添加另一个函数来避免闭包本身,进而阻止了泄漏。

清单 8. 避免闭包自身


结束语

本文解释了循环引用是如何导致 JavaScript 中的内存泄漏的 —— 尤其是在结合了闭包的情况下。您还了解了涉及到循环引用的一些常见内存泄漏模式以及应对这些泄漏模式的几种简单方式。

作者简介

Abhijeet Bhattacharya 是 IBM 印度软件实验室的一名系统工程师。在过去三年中,他一直是 OS/2 IBM Web Browser 支持团队中的一员。他也具有系统管理领域的相关经验,并参与过 IBM Pegasus 开源创新项目。他目前工作的重点包括分布式计算和 SARPC。他拥有 Rajiv Gandhi Technical University 的工程学士学位。

Kiran Shivarama Sundar 是 IBM 印度软件实验室的一名系统工程师。在过去三年中,他一直是 OS/2 IBM Web Browser 支持团队中的一员。他同时也具有诸多其他项目的工作经验,包括为 Apache Tuscany Open Source Project 开发命令行工具以及为 IBM 的 EPCIS 团队开发 RFIDIC Installer。目前,Kiran 加入了 IBM WebSphere Adapters 支持团队,负责提供对 JMS 和 MQ 适配器的支持。他已成功获得了 Sun Certified Java Programmer、Sun Certified Web Component Developer 和 Sun Certified Business Component Developer 的认证。他目前所关注的领域包括 Java、J2EE、Web 服务和 SOA。他拥有 Visweshwaraya Technology University 的工程学士学位。
Javascript 相关文章推荐
javascript 对象定义方法 简单易学
Mar 22 Javascript
Javascript闭包(Closure)详解
May 05 Javascript
基于Jquery制作图片文字排版预览效果附源码下载
Nov 18 Javascript
浅析javascript的return语句
Dec 15 Javascript
JQuery 的跨域方法推荐_可跨任何网站
May 18 Javascript
BootStrap智能表单实战系列(三)分块表单配置详解
Jun 13 Javascript
JavaScript实现广告弹窗效果
Aug 09 Javascript
Angular2 自定义validators的实现方法
Jul 05 Javascript
使用Bootstrap和Vue实现用户信息的编辑删除功能
Oct 25 Javascript
vue实例中data使用return包裹的方法
Aug 27 Javascript
JS实现页面跳转与刷新的方法汇总
Aug 30 Javascript
p5.js临摹动态图形实现方法详解
Oct 23 Javascript
封装好的省市地区联动控件附下载
Aug 13 #Javascript
分享别人写的一个小型js框架
Aug 13 #Javascript
javascript下查找父节点的简单方法
Aug 13 #Javascript
根据地区不同显示时间的javascript代码
Aug 13 #Javascript
解决使用attachEvent函数时,this指向被绑定的元素的问题的方法
Aug 13 #Javascript
Track Image Loading效果代码分析
Aug 13 #Javascript
不错的JS中变量相关的细节分析
Aug 13 #Javascript
You might like
php XMLWriter类的简单示例代码(RSS输出)
2011/09/30 PHP
带你了解PHP7 性能翻倍的关键
2015/11/19 PHP
php array_multisort 对数组进行排序详解及实例代码
2016/10/27 PHP
用Javscript实现表单复选框的全选功能
2007/05/25 Javascript
关于extjs4如何获取grid修改后的数据的问题
2013/08/07 Javascript
JSuggest自动匹配下拉框使用方法(示例代码)
2013/12/27 Javascript
做好七件事帮你提升jQuery的性能
2014/02/06 Javascript
jQuery实现自定义下拉列表
2015/01/05 Javascript
JavaScript插件化开发教程 (二)
2015/01/27 Javascript
函数window.open实现关闭所有的子窗口
2015/08/03 Javascript
JavaScript获取当前url根目录(路径)
2016/06/17 Javascript
详解vue 中使用 AJAX获取数据的方法
2017/01/18 Javascript
使用JS在浏览器中判断当前网络连接状态的几种方法
2017/05/05 Javascript
JavaScript选取(picking)和反选(rejecting)对象的属性方法
2017/08/16 Javascript
angularjs中$http异步上传Excel文件方法
2018/02/23 Javascript
JS实现的文件拖拽上传功能示例
2018/05/21 Javascript
详解VUE调用本地json的使用方法
2019/05/15 Javascript
vue中的过滤器实例代码详解
2019/06/06 Javascript
Vue实现购物车实例代码两则
2020/05/30 Javascript
Python使用Flask框架同时上传多个文件的方法
2015/03/21 Python
Python多线程编程简单介绍
2015/04/13 Python
浅析Python 中整型对象存储的位置
2016/05/16 Python
Python的IDEL增加清屏功能实例
2017/06/19 Python
利用pandas进行大文件计数处理的方法
2018/07/25 Python
Python抽象和自定义类定义与用法示例
2018/08/23 Python
python2.7 安装pip的方法步骤(管用)
2019/05/05 Python
Django中使用 Closure Table 储存无限分级数据
2019/06/06 Python
Python3从零开始搭建一个语音对话机器人的实现
2019/08/23 Python
Python 解决OPEN读文件报错 ,路径以及r的问题
2019/12/19 Python
pytorch1.0中torch.nn.Conv2d用法详解
2020/01/10 Python
python 安装移动复制第三方库操作
2020/07/13 Python
Python中bisect的用法及示例详解
2020/07/20 Python
美国专业汽车音响和移动电子产品零售商:Car Toys
2019/05/13 全球购物
学党史心得体会
2014/09/05 职场文书
施工员岗位职责
2015/02/10 职场文书
2015年“7.11”世界人口日宣传活动方案
2015/05/06 职场文书