权威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 相关文章推荐
jquery sortable的拖动方法示例详解
Jan 16 Javascript
javascript 获取元素样式必杀技
May 04 Javascript
jQuery Mobile开发中日期插件Mobiscroll使用说明
Mar 02 Javascript
JavaScript6 let 新语法优势介绍
Jul 15 Javascript
AngularJS 自定义指令详解及示例代码
Aug 17 Javascript
jquery获取点击控件的绝对位置简单实例
Oct 13 Javascript
jQ处理xml文件和xml字符串的方法(详解)
Nov 22 Javascript
微信小程序自定义组件
Aug 16 Javascript
浅谈vue中数据双向绑定的实现原理
Sep 14 Javascript
javascript少儿编程关于返回值的函数内容
May 27 Javascript
Js中使用正则表达式验证输入是否有特殊字符
Sep 07 Javascript
Openlayers绘制地图标注
Sep 28 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
全国FM电台频率大全 - 15 山东省
2020/03/11 无线电
zf框架的zend_cache缓存使用方法(zend框架)
2014/03/14 PHP
PHP中可以自动分割查询字符的Parse_str函数使用示例
2014/07/25 PHP
php获取根域名方法汇总
2014/10/28 PHP
ThinkPHP连接数据库的方式汇总
2014/12/05 PHP
ThinkPHP中url隐藏入口文件后接收alipay传值的方法
2014/12/09 PHP
Laravel中扩展Memcached缓存驱动实现使用阿里云OCS缓存
2015/02/10 PHP
PHP实现的文件操作类及文件下载功能示例
2016/12/24 PHP
游戏人文件夹程序 ver 3.0
2006/07/14 Javascript
Javascript 高阶函数使用介绍
2015/06/15 Javascript
动态加载js文件简单示例
2016/04/21 Javascript
ionic实现滑动的三种方式
2016/08/27 Javascript
js实现不提示直接关闭网页窗口
2017/03/30 Javascript
vue实现仿淘宝结账页面实例代码
2017/11/08 Javascript
基于vue-cli配置lib-flexible + rem实现移动端自适应
2017/12/26 Javascript
elementui之el-tebs浏览器卡死的问题和使用报错未注册问题
2019/07/06 Javascript
js禁止查看源文件屏蔽Ctrl+u/s、F12、右键等兼容IE火狐chrome
2020/10/01 Javascript
解决vue使用vant轮播组件swipe + flex时文字抖动问题
2021/01/07 Vue.js
Handtrack.js库实现实时监测手部运动(推荐)
2021/02/08 Javascript
Python中使用装饰器和元编程实现结构体类实例
2015/01/28 Python
Django中URLconf和include()的协同工作方法
2015/07/20 Python
Python之用户输入的实例
2018/06/22 Python
python 自定义对象的打印方法
2019/01/12 Python
解决Tensorflow sess.run导致的内存溢出问题
2020/02/05 Python
Python爬虫工具requests-html使用解析
2020/04/29 Python
Django如何实现防止XSS攻击
2020/10/13 Python
Bealls Florida百货商店:生活服饰、家居装饰和鞋子
2018/02/23 全球购物
印度服装购物网站:Limeroad
2018/09/26 全球购物
三星加拿大官方网上商店:Samsung CA
2020/12/18 全球购物
给老婆的搞笑检讨书
2014/01/12 职场文书
创业计划书如何编写
2014/02/06 职场文书
竞聘报告优秀范文
2014/11/06 职场文书
个人学习总结范文
2015/02/15 职场文书
如何用六步教会你使用python爬虫爬取数据
2022/04/06 Python
分享很少见很有用的SQL功能CORRESPONDING
2022/08/05 MySQL
Zabbix6通过ODBC方式监控Oracle 19C的详细过程
2022/09/23 Servers