JavaScript:4个常见的内存泄露
什么是内存泄漏
内存泄漏基本上就是不再被应用需要的内存,由于某种原因,没有被归还给操作系统或者进入可用内存池。
编程语言喜欢不同的管理内存方式。然而,一段确定的内存是否被使用是一个不可判断的问题。换句话说,只有开发者才能弄清楚,是否一段内存可以被还给操作系统。
某些编程语言为开发者提供了释放内存功能。另一些则期待开发者清楚的知道一段内存什么时候是没用的。Wikipedia有一篇非常好的关于内存管理的文章。
4种常见的JavaScript内存泄漏
1:全局变量
JavaScript用一个有趣的方式管理未被声明的变量:对未声明的变量的引用在全局对象里创建一个新的变量。在浏览器的情况下,这个全局对象是 window 。换句话说:
- function foo(arg) {
- bar = "some text";
- }
等同于
- function foo(arg) {
- window.bar = "some text";
- }
如果 bar 被假定只在 foo 函数的作用域里引用变量,但是你忘记了使用 var 去声明它,一个意外的全局变量就被声明了。
在这个例子里,泄漏一个简单的字符串不会造成很大的伤害,但是它确实有可能变得更糟。
另外一个意外创建全局变量的方法是通过 this :
- function foo() {
- this.var1 = "potential accidental global";
- }
- // Foo作为函数调用,this指向全局变量(window)
- // 而不是undefined
- foo();
为了防止这些问题发生,可以在你的JaveScript文件开头使用 'use strict'; 。这个可以使用一种严格的模式解析JavaScript来阻止意外的全局变量。
除了意外创建的全局变量,明确创建的全局变量同样也很多。这些当然属于不能被回收的(除非被指定为null或者重新分配)。特别那些用于暂时存储数据的全局变量,是非常重要的。如果你必须要使用全局变量来存储大量数据,确保在是使用完成之后为其赋值 null或者重新赋其他值。
2: 被遗忘的定时器或者回调
在JavaScript中使用 setInterval 是十分常见的。
大多数库,特别是提供观察器或其他接收回调的实用函数的,都会在自己的实例无法访问前把这些回调也设置为无法访问。但涉及 setInterval 时,下面这样的代码十分常见:
- var serverData = loadData();
- setInterval(function() {
- var renderer = document.getElementById('renderer');
- if(renderer) {
- renderer.innerHTML = JSON.stringify(serverData);
- }
- }, 5000); //每5秒执行一次
定时器可能会导致对不需要的节点或者数据的引用。
renderer 对象在将来有可能被移除,让interval处理器内部的整个块都变得没有用。但由于interval仍然起作用,处理程序并不能被回收(除非interval停止)。如果interval不能被回收,它的依赖也不可能被回收。这就意味着 serverData ,大概保存了大量的数据,也不可能被回收。
在观察者的情况下,在他们不再被需要(或相关对象需要设置成不能到达)的时候明确的调用移除是非常重要的。
在过去,这一点尤其重要,因为某些浏览器(旧的IE6)不能很好的管理循环引用(更多信息见下文)。如今,大部分的浏览器都能而且会在对象变得不可到达的时候回收观察处理器,即使监听器没有被明确的移除掉。然而,在对象被处理之前,要显式地删除这些观察者仍然是值得提倡的做法。例如:
- var element = document.getElementById('launch-button');
- var counter = 0;
- function onClick(event) {
- counter++;
- element.innerHtml = 'text ' + counter;
- }
- element.addEventListener('click', onClick);
- // 做点事
- element.removeEventListener('click', onClick);
- element.parentNode.removeChild(element);
- // 当元素被销毁
- //元素和事件都会即使在老的浏览器里也会被回收
如今的浏览器(包括IE和Edge)使用现代的垃圾回收算法,可以立即发现并处理这些循环引用。换句话说,先调用 removeEventListener 再删节点并非严格必要。
jQuery等框架和插件会在丢弃节点前删除监听器。这都是它们内部处理,以保证不会产生内存泄漏,甚至是在有问题的浏览器(没错,IE6)上也不会。
3: 闭包
闭包是JavaScript开发的一个关键方面:一个内部函数使用了外部(封闭)函数的变量。由于JavaScript运行时实现的不同,它可能以下面的方式造成内存泄漏:
- var theThing = null;
- var replaceThing = function () {
- var originalThing = theThing;
- var unused = function () {
- if (originalThing) // 引用'originalThing'
- console.log("hi");
- };
- theThing = {
- longStr: new Array(1000000).join('*'),
- someMethod: function () {
- console.log("message");
- }
- };
- };
- setInterval(replaceThing, 1000);
这段代码做了一件事:每次 ReplaceThing 被调用, theThing 获得一个包含大数组和新的闭包( someMethod )的对象。同时,变量 unused 保持了一个引用 originalThing ( theThing 是上次调用 replaceThing 生成的值)的闭包。已经有点困惑了吧?最重要的事情是 一旦为同一父域中的作用域产生闭包,则该作用域是共享的。
这里,作用域产生了闭包, someMethod 和 unused 共享这个闭包中的内存。 unused 引用了 originalThing 。尽管 unused 不会被使用, someMethod 可以通过 theThing 来使用 replaceThing 作用域外的变量(例如某些全局的)。而且 someMethod 和 unused 有共同的闭包作用域, unused 对 originalThing 的引用强制 oriiginalThing 保持激活状态(两个闭包共享整个作用域)。这阻止了它的回收。
当这段代码重复执行,可以观察到被使用的内存在持续增加。垃圾回收运行的时候也不会变小。从本质上来说,闭包的连接列表已经创建了(以 theThing 变量为根),这些闭包每个作用域都间接引用了大数组,导致大量的内存泄漏。
这个问题被Meteor团队发现,他们有描述了闭包大量的细节。
4: DOM外引用
有的时候在数据结构里存储DOM节点是非常有用的,比如你想要快速更新一个表格几行的内容。此时存储每一行的DOM节点的引用在一个字典或者数组里是有意义的。此时一个DOM节点有两个引用:一个在dom树中,另外一个在字典中。如果在未来的某个时候你想要去移除这些排,你需要确保两个引用都不可到达。
- var elements = {
- button: document.getElementById('button'),
- image: document.getElementById('image')
- };
- function doStuff() {
- image.src = 'http://example.com/image_name.png';
- }
- function removeImage() {
- //image是body元素的子节点
- document.body.removeChild(document.getElementById('image'));
- //这个时候我们在全局的elements对象里仍然有一个对#button的引用。
- //换句话说,buttom元素仍然在内存中而且不能被回收。
- }
当涉及DOM树内部或子节点时,需要考虑额外的考虑因素。例如,你在JavaScript中保持对某个表的特定单元格的引用。有一天你决定从DOM中移除表格但是保留了对单元格的引用。人们也许会认为除了单元格其他的都会被回收。实际并不是这样的:单元格是表格的一个子节点,子节点保持了对父节点的引用。确切的说,JS代码中对单元格的引用造成了 整个表格被留在内存中了 ,所以在移除有被引用的节点时候要当心。
我们在SessionStack努力遵循这些最佳实践,因为:
一旦你整合essionStack到你的生产应用中,它就开始记录所有的事情:DOM变化、用户交互、JS异常、堆栈跟踪、失败的网络请求、调试信息,等等。
通过SessionStack,你可以回放应用中的问题,看到问题对用户的影响。所有这些都不会对你的应用产生性能的影响。因为用户可以重新加载页面或者在应用中跳转,所有的观察者、拦截器、变量分配都必须合理处置。以免造成内存泄漏,也预防增加整个应用的内存占用。
JavaScript:4个常见的内存泄露的更多相关文章
- JavaScript 中 4 种常见的内存泄露陷阱
了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读一读吧 ...
- JavaScript 中常见的内存泄露陷阱(摘)
内存泄露是每个开发者最终都不得不面对的问题.即便使用自动内存管理的语言,你还是会碰到一些内存泄漏的情况.内存泄露会导致一系列问题,比如:运行缓慢,崩溃,高延迟,甚至一些与其他应用相关的问题. 什么是内 ...
- 谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用
其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题.一.什么是内存泄露(memory leak)?内存泄露不是指内存坏了,也不是指内存没插稳漏出来 ...
- .NET中常见的内存泄露问题——GC、委托事件和弱引用
一.什么是内存泄露(memory leak)? 内存泄露不是指内存坏了,也不是指内存没插稳漏出来了,简单来说,内存泄露就是在你期待的时间内你程序所占用的内存没有按照你想象中的那样被释放. 因此什么是你 ...
- 对开发中常见的内存泄露,GDI泄露进行检测
对开发中常见的内存泄露,GDI泄露进行检测 一.GDI泄露检测方法: 在软件测试阶段,可以通过procexp.exe 工具,或是通过任务管理器中选择GDI对象来查看软件GDI的对象是使用情况. 注意点 ...
- 分享.net常见的内存泄露及解决方法
分享.net常见的内存泄露及解决方法 关于内存泄漏的问题,之前也为大家介绍过,比如:<C++中内存泄漏的检测方法介绍>,是关于C++内存泄漏的.今天为大家介绍的是关于.NET内存泄漏的问题 ...
- Andorid 内存溢出与内存泄露,几种常见导致内存泄露的写法
内存泄露,大部分是因为程序的逻辑不严谨,但是又可以跑通顺,然后导致的,内存溢出不会报错,如果不看日志信息是并不知道有泄露的.但是如果一直泄露,然后最终导致的内存溢出,仍然会使程序挂掉.内存溢出大部分是 ...
- Android开发中常见的内存泄露案例以及解决方法总结
1.单例模式引起的内存泄露 由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏如错误代码示例: public class Use ...
- Android下常见的内存泄露 经典
转自:http://www.linuxidc.com/Linux/2011-10/44785.htm 因为Android使用Java作为开发语言,很多人在使用会不注意内存的问题. 于是有时遇到程序运行 ...
- JavaScript 的垃圾回收与内存泄露
JavaScript采用垃圾自动回收机制,运行时环境会自动清理不再使用的内存,因此javascript无需像C++等语言一样手动释放无用内存. 在这之前先说一下垃圾回收的两种方式:引用计数与标记清除. ...
随机推荐
- Linux硬链接和软连接
硬链接(hard link): A是B的硬链接(A和B都是文件名),则A的目录项中的inode节点号与B的目录项中的inode节点号相同,即一个inode节点对应两个不同的文件名,两个文件名指向同一个 ...
- HTTP请求封装Java工具类
装载自:http://www.open-open.com/lib/view/open1384844838743.html package com.wiker; import java.io.Buffe ...
- 洛谷P1248 加工生产调度
流水作业调度问题 有\(N\)个作业要在两台机器\(M_1\)和\(M_2\)组成的流水线上完成加工.每个作业\(i\)都必须先花时间\(a_i\)在\(M_1\)上加工,然后花时间\(b_i\)在\ ...
- AnalyticDB for MySQL 3.0 技术架构解析
企业数据需求不断变化,近年来变化趋势日益明显,从数据的3V特性看:体积,速度和变化:Big Data强调数据量,PB级以上,是静态数据.而Fast Data在数据量的基础上,意味着速度和和变化,意味着 ...
- Java大数类BigDecimal及八种舍入模式的介绍
BigDecimal的引入 在利用Java编程语言开发银行.金融类等需要对数值进行高精度计算的软件时,我们经常使用BigDecimal和BigInteger这两个大数据类,而不是常见的int.long ...
- Vagrant-安装教程及常见问题
http://ju.outofmemory.cn/entry/346215 前言: Vagrant是一个基于Ruby的工具,用于创建和部署虚拟化开发环境. 它的主要意义是让所有开发人员都使用和线上服务 ...
- jsp内建对象的作用域
- oracle函数 RAWTOHEX(c1)
[功能]将一个二进制构成的字符串转换为十六进制 [参数]c1,二进制的字符串 [返回]字符串 [示例] select RAWTOHEX('A123') from dual;
- Python基础:07迭代器
迭代器是在版本 2.2 被加入Python 的,它为类序列对象提供了一个类序列的接口.Python 的迭代无缝地支持序列对象,而且它还允许迭代非序列类型,包括用户定义的对象.它的出现,对列表迭代.字典 ...
- LeetCode Weekly Contest 6
leetcode现在每周末举办比赛,这是今天上午参加的比赛的题解.题目难度不算大,两个easy,一个medium,一个hard.hard题之前接触过,所以做得比较顺利. 1. Sum of Left ...