原文在https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management.基本保持了平译,并在一些地方作了概念解释。(转载请注明出处)javascript
高级语言,好比C,有底层的内存管理原语如malloc()和free()。可是对于Javascript来讲,变量(string、object等等)被建立的时候分配内存,等变量再也不被使用的时候,内存被"自动"释放。这个自动释放的过程叫作"垃圾回收"。“自动”这个词常令人困惑而且给了javascript(以及其余的高级语言)开发者他们不须要关心内存管理的印象。这是错误的。java
忽略具体的变成语言,内存的生命周期一般是这样的:程序员
第二部分在全部的语言里都是明显的。第一和第三部分在低级语言里是明显的,可是在高级语言如JavaScript里是很是隐秘的。算法
为了避免让程序的书写者操心内存分配问题,JavaScript随着变量的声明自动分配了内存。数组
var n = 123; //为1个数字分配了内存 var s = 'azerty';//为一个字符串分配了内存 var o = { a:1, b:null };//为对象和里边的键值分配了内存 //(和对象同样),为数组和里边的元素分配了内存 var a = [1,null,'abra']; function f(a){ return a + 2; }//为一个函数分配了内存(函数是可调用对象) //函数表达式也做为1个对象被分配了内存 //译者注:函数的表达式指的是下面的匿名函数的定义部分,JavaScript的函数能够做为函数的参数传入,由于他是一种特殊的对象 someElement.addEventListener('click', function() { someElement.style.backgroundColor = 'blue'; }, false);
一些函数调用的结果分配了内存浏览器
var d = new Date(); // 建立了一个Date对象,并分配了内存 var e = document.createElement('div'); // 建立了一个Dom元素,分配了内存
一些函数会分配新的变量或对象:并发
var s = 'azerty'; var s2 = s.substr(0, 3); // s2 是一个新的字符串 // 由于字符串是不可变对象, // JavaScript 可能不会再为s2分配内存, 而仅仅让s2记录0-3这个区间 var a = ['ouais ouais', 'nan nan']; var a2 = ['generation', 'nan nan']; var a3 = a.concat(a2); // 新的数组s3有4个元素,链接了a和a2
使用内存基本上就是读写已分配的内存。好比读写变量的值,对象的属性,又或者给函数传递参数(译者:这里是说变量做为参数传递到函数的行为,就是使用这个变量内存的过程)。dom
这个阶段会产生许多内存管理的问题。最难的问题是找到何时“已分配的内存再也不须要”,这要求开发者去检测什么地方的内存再也不须要,而后释放它们。函数
高级语言嵌入了一个叫作“垃圾回收”的功能,它的做用是追踪内存分配,当发现一块已分配的内存再也不须要的时候,自动释放它。这是一个近似的过程,由于“判断一段内存是否还须要保留”是不可断定的(undecidable .can't be solved by an algorithm.这里能够理解为,分配的内存还用不用只有程序员主观的知道,若是他忘了,会致使内存泄漏,若是程序员手贱,释放的早了,再访问这块内存就会致使内存访问错误。语言的垃圾回收机制更像是一个第三者,虽然他很聪明,算法很强大,很能揣测程序意图,可是毕竟不是本人)。code
垃圾回收算法的主要理论依据是一个叫“引用”的概念。在内存管理的上下文中,若是一个对象有权利访问另外一个对象(的内存,不管是显式仍是隐式),会让前者去引用后者。好比,Javascript对象有对他的原型隐式的引用,和对它属性显式的引用。
在内存管理的上下文中,“对象”的概念比一般的JavaScript的对象(Object)更为宽泛,甚至包含函数做用域,或者全局的词法做用域。
这是最简单的垃圾回收算法。这种算法将“一个内存中对象再也不被须要”简化为"一个内存对象再也不被别的对象引用"。若是一个对象被引用的数量为0,这个对象就被认为应该被回收了。(译者注:大多数此类算法都要求被管理的全部对象都有一个相似referenceCount的属性,标识本身被引用了多少次)。
var o = { a:{ b:2 } }; //2个对象被建立出来,1给被引用为对象的属性,而后外部的对象被变量o引用。显然他们都不会被回收 var o2 = o;//变量o2又引用了o引用的对象 o = 1;//如今变量o再也不引用以前的对象,以前的对象只被o2引用了 var oa = o2.a;//如今做为对象属性的对象,又被变量oa引用了 o2 = 'yo';//如今最初被o引用的对象已经再被引用了,能够被回收,可是它的a属性还被oa引用,因此还不能被释放 oa = null;//如今做为a属性的对象也再也不被引用了,最初被o引用的对象能够被回收释放了。
当引用出现循环,这种算法就会出现限制。下面的例子里,2个对象被建立出来,而且彼此被另外一个引用,致使循环引用。函数执行完成后,他们会离开函数做用域,再也不起做用了,能够被释放了。可是,引用计数算法认为他们2个都被引用了一次(函数接收后,变量o,o2会解掉对对象的引用),不能被回收。
function f() { var o = {}; var o2 = {}; o.a = o2; // o引用的对象的属性a 引用 o2 o2.a = o; // o2引用的对象的属性a 引用 o return 'azerty'; } f();
IE6和IE7使用引用计数管理DOM对象,因此循环引用是常见的致使内存泄漏的错误。
var div; window.onload = function() { div = document.getElementById('myDivElement'); div.circularReference = div; div.lotsOfData = new Array(10000).join('*'); };
这个例子里,dom元素"myDivElement"被本身的circularReference属性所引用(div.circularReference = div;)。若是这个属性没有明确的移除或者置为null,即便myDivElement这个元素从DOM树里移除了,引用计数垃圾收集器还将老是拥有至少一个这个元素的引用,而且一直存在于内存中。若是DOM元素持有大量数据(好比这个例子里的lotsOfData属性),这个数据消耗的内存将不会被释放回收。
这个算法将"一个内存中对象再也不被须要"简化为"一个内存对象不可达"。(译者注:原文是an object is unreachable。内存中的对象,若是程序没法经过一些线索,如引用关系找到它,它就没有存在的意义,是不可达的)。这种算法假定知道一组叫作根的对象,周期性的从这些“根”开始,查找到全部被根引用的对象,而后从这些对象开始,递归的查找下去。所以,从根开始查找的过程当中,垃圾收集器将会找到全部可达的内存对象和不可达的内存对象。
这个算法比上一个要好,由于“一个被0引用”的内存对象能够推导出这个内存对象不可达,相反,出现循环引用的时候,反推却不成立。
截止2012年,全部的现代浏览器都搭载了标记-清除垃圾回收器。过去几年里,JavaScript 的垃圾回收领域(分代/增量/并发/并行垃圾回收)技术的更新实现了这种算法的进步,可是对垃圾回收算法自己:何时"一个内存对象再也不须要"这个概念的处理,并无多大进步。(原文:but not improvements over the garbage collection algorithm itself nor its reduction of the definition of when "an object is not needed anymore).
第一个例子里,函数结束后,2个对象再也不被由全局对象可达的对象所引用,所以,他们被垃圾回收器认为是不可达的。
即便这被标注为一个限制,可是在实践中不多遇到,这也是为何不多有人会常常关注垃圾收集的缘由(说的是,在当前标记清除的技术下,程序员们再也不关注垃圾回收这个问题了)。