权威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 相关文章推荐
js注意img图片的onerror事件的分析
Jan 01 Javascript
基于jquery的滚动条滚动固定div(附演示下载)
Oct 29 Javascript
JavaScript改变HTML元素的样式改变CSS及元素属性
Nov 12 Javascript
JS面向对象编程详解
Mar 06 Javascript
Bootstrap学习笔记之js组件(4)
Jun 12 Javascript
jQuery禁用快捷键例如禁用F5刷新 禁用右键菜单等的简单实现
Aug 31 Javascript
javascript入门之window对象【新手必看】
Nov 22 Javascript
基于js实现checkbox批量选中操作
Nov 22 Javascript
Avalonjs 实现简单购物车功能(实例代码)
Feb 07 Javascript
微信小程序按钮点击跳转页面详解
May 06 Javascript
JavaScript创建表格的方法
Apr 13 Javascript
VSCode写vue项目一键生成.vue模版,修改定义其他模板的方法
Apr 17 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文件向另一个地址post数据,不用表单和隐藏的变量的
2007/03/06 PHP
PHP实现的比较完善的购物车类
2014/12/02 PHP
IE中直接运行显示当前网页中的图片 推荐
2006/08/31 Javascript
客户端脚本中常常出现的一些问题和调试技巧
2007/01/09 Javascript
javascript 的Document属性和方法集合
2010/01/25 Javascript
JS Excel读取和写入操作(模板操作)实现代码
2010/04/11 Javascript
用JS实现3D球状标签云示例代码
2013/12/01 Javascript
JS取request值以及自动执行使用示例
2014/02/24 Javascript
JS数组的赋值介绍
2014/03/10 Javascript
基于BootStrap实现局部刷新分页实例代码
2016/08/08 Javascript
微信小程序 解析网页内容详解及实例
2017/02/22 Javascript
layui.js实现的表单验证功能示例
2017/11/15 Javascript
layui select动态添加option的实例
2018/03/07 Javascript
使用jquery DataTable和ajax向页面显示数据列表的方法
2018/08/09 jQuery
Element UI 自定义正则表达式验证方法
2018/09/04 Javascript
Vue form表单动态添加组件实战案例
2019/09/02 Javascript
vue遍历对象中的数组取值示例
2019/11/07 Javascript
JS实现导航栏楼层特效
2020/01/01 Javascript
vue 使用v-for进行循环的实例代码详解
2020/02/19 Javascript
vue与iframe之间的信息交互的实现
2020/04/08 Javascript
使用React-Router实现前端路由鉴权的示例代码
2020/07/26 Javascript
原生JavaScript实现购物车
2021/01/10 Javascript
在Pycharm中执行scrapy命令的方法
2019/01/16 Python
Python人脸识别第三方库face_recognition接口说明文档
2019/05/03 Python
Python 使用folium绘制leaflet地图的实现方法
2019/07/05 Python
python中for循环把字符串或者字典添加到列表的方法
2019/07/20 Python
python实现人机猜拳小游戏
2020/02/03 Python
Python3 socket即时通讯脚本实现代码实例(threading多线程)
2020/06/01 Python
django rest framework 自定义返回方式
2020/07/12 Python
Gerry Weber德国官网:优质女性时装,德国最大的时装公司之一
2019/11/02 全球购物
MIS软件工程师的面试题
2016/04/22 面试题
工程概预算专业毕业生求职信
2013/10/04 职场文书
服务标兵事迹材料
2014/05/04 职场文书
巾帼标兵事迹材料
2014/12/26 职场文书
颐和园导游词400字
2015/01/30 职场文书
WebRTC记录音视频流(web技术分享)
2022/02/24 Javascript