- 浏览: 5181732 次
- 性别:
- 来自: 天津
-
博客专栏
-
-
实战 Groovy
浏览量:29509
文章分类
- 全部博客 (639)
- 代码之谜 (6)
- JavaScript quirks (5)
- 程序员 (92)
- Java (93)
- BT编程 (7)
- html/css (64)
- Groovy&Grails (42)
- Android (20)
- C/C++ (5)
- PHP/Perl/Python (46)
- 经典文章 (51)
- CodeIgniter (14)
- JQuery (10)
- 笑话 (4)
- 其他 (32)
- javascript (69)
- 云计算 (0)
- html5 (7)
- 面试 (8)
- google (3)
- nosql (2)
- nodejs (11)
- go (5)
- erlang (1)
- 小常识 (3)
- 冷知识 (5)
- database (4)
- web (12)
- 架构 (12)
- Exception (0)
最新评论
-
jqw1992:
https://www.chromefor.com/libra ...
[福利] 开发者必备的 Chrome 插件——ChromeSnifferPlus -
litjerk:
初步算了一下,目前最最精简的Win98版是5M,他5个小时多敲 ...
让人目瞪口呆的三位世界级电脑大师 -
379855529:
。。似乎重点没说NIO啊,前面基础只是铺垫的很好的,可是我要的 ...
Java NIO与IO的详细区别(通俗篇) -
springmvc_springjpa:
spring mvc demo教程源代码下载,地址:http: ...
一步步开发 Spring MVC 应用 -
匡建武:
Good
四个程序员的一天
本文的诞生,源自近期打算做的一个关于javascript中的闭包的专题,由于需要解析闭包对垃圾回收的影响,特此针对不同的javascript引擎,做了相关的测试。
为了能从本文中得到需要的知识,看本文前,请明确自己知道闭包的概念,并对垃圾回收的常用算法有一定的了解。
问题的提出
假设有如下的代码:
function outer() {
var largeObject = LargeObject.fromSize('100MB');
return function() {
console.log('inner');
};
}
var inner = outer();
在这一段代码中,outer
函数和inner
函数间会形成一个闭包,致使inner
函数能够访问到largeObject
,但是显然inner
并没有访问largeObject
,那么在闭包中的largeObject
对象是否能被回收呢?
如果引入更复杂的情况:
function outer() {
var largeObject = LargeObject.fromSize('100MB');
var anotherLargeObject = LargeObject.fromSize('100MB');
return function() {
largeObject.work();
console.log('inner');
};
}
var inner = outer();
首先一个显然的概念是largeObject
肯定不能被回收,因为inner
确实地需要使用它。但是anotherLargeObject
又能不能被回收呢?它将跟随largeObject
一起始终存在,还是和largeObject
分离,独立地被回收呢?
测试方法
带着这个疑问,对现有的几款现代javascript引擎分别进行了测试,参与测试的有:
- IE8自带的JScript.dll
- IE9自带的Chakra
- Opera 11.60自带的Carakan
- Chrome 16.0.912.63自带的V8(3.6.6.11)
- Firefox 9.0.1自带的SpiderMonkey
测试的基本方案是,使用类似以下的代码:
function outer() {
var largeObject = LargeObject.fromSize('100MB');
return function() {
debugger;
};
}
var inner = outer();
通过各浏览器的开发者工具(Developer Tools、Firebug、Dragonfly等),在断点处停止javascript的执行,并通过控制台或本地变量查看功能检查largeObject
的值,如果其值存在,则认为GC并没有回收该对象。
对于部分浏览器(特别是IE),考虑到对脚本执行有2种模式(执行模式和调试模式,IE通过开发者工具的Script面板中的“Start Debugging”按钮切换),在调试模式下才会命中断点,但是调试模式下可能存在不同的引擎优化方案,因此采用内存比对的方式进行测试。即打开资源浏览器,在var inner = outer();
一行后强制执行一次垃圾回收(IE使用window.CollectGarbage()
;Opera使用window.opera.collect();
),查看内存的变化。如果内存始终有100MB的占用,没有明显的下降现象,则认为GC并没有回收该对象。
对于用例的设计,由于从ECMAScript标准中可以得知,所有的变量访问是通过一个LexicalEnvironment对象进行的,因此目标在于在不同的LexicalEnvironment结构下进行测试。从标准中,搜索LexicalEnvironment不难得出能够改变LexicalEnvironment结构的情况有以下几种:
- 进入一个函数。
- 进入一段
eval
代码。 - 使用
with
语句。 - 使用
catch
语句。
因此以下将针对这4种情况,进行多用例的测试。
测试过程级结果
基本测试
使用代码
function outer() {
var largeObject = LargeObject.fromSize('100MB');
return function() {
debugger;
};
}
var inner = outer();
inner();
测试结果
- JScript.dll – 不回收,内存无下降趋势。
- Chakra – 回收,内存会恢复到
outer
函数执行前的状态。 - Carakan – 不回收,内存无下降趋势。
- V8 – 回收,访问
largeObject
抛出ReferenceError。 - SpiderMonkey – 回收,访问
largeObject
得到undefined
。
结论
当一个函数outer
返回另一个函数inner
时,Chakra、V8和SpiderMonkey会对outer
中声明,但inner
中不使用的变量进行回收,其中V8直接将变量从LexicalEnvironment上解除绑定,而SpiderMonkey仅仅将变量的值设为undefined
,并不解除绑定。
多个变量的情况
使用代码
function outer() {
var largeObject = LargeObject.fromSize('100MB');
var anotherLargeObject = LargeObject.fromSize('100MB');
return function() {
largeObject;
debugger;
};
}
var inner = outer();
inner();
测试结果
- JScript.dll – 不回收,内存无下降趋势。
- Chakra – 回收
anotherLargeObject
,内存会回到outer
调用前并增加100MB左右。 - Carakan – 不回收,内存无下降趋势。
- V8 – 回收,访问
largeObject
能得到正确的值,访问anotherLargeObject
抛出ReferenceError。 - SpiderMonkey – 回收,访问
largeObject
能得到正确的值,访问anotherLargeObject
得到undefined
。
结论
当一个LexicalEnvironment上存在多个变量绑定时,Chakra、V8和SpiderMonkey会针对不同的变量判断是否有被使用,该判断方法是扫描返回的函数inner
的源码来实现的,随后会将没有被inner
使用的变量从LexicalEnvironment中解除绑定(同样的,SpiderMonkey不解除绑定,仅赋值为undefined
),而剩下的变量继续保留。
eval
的影响
使用代码
function outer() {
var largeObject = LargeObject.fromSize('100MB');
return function() {
eval('');
debugger;
};
}
var inner = outer();
inner();
测试结果
- JScript.dll – 不回收,内存无下降趋势。
- Chakra – 不回收,内存无下降趋势。
- Carakan – 不回收,内存无下降趋势。
- V8 – 不回收,访问
largeObject
可得到正确的值。 - SpiderMonkey – 不回收,访问
largeObject
可得到正确的值。
结论
如果返回的inner
函数中有使用eval
函数,则不LexicalEnvironment中的任何变量进行解除绑定的操作,保留所有变量的绑定,以避免产生不可预期的结果。
间接调用eval
使用代码
function outer() {
var largeObject = LargeObject.fromSize('100MB');
return function() {
window.eval('');
debugger;
};
}
var inner = outer();
inner();
测试结果
- JScript.dll – 不回收,内存无下降趋势。
- Chakra – 回收,内存会恢复到
outer
函数执行前的状态。 - Carakan – 不回收,内存无下降趋势。
- V8 – 回收,访问
largeObject
抛出ReferenceError。 - SpiderMonkey – 回收,访问
largeObject
得到undefined
。
结论
由于ECMAScript规定间接调用eval
时,代码将在全局作用域下执行,是无法访问到largeObject
变量的。因此对于间接调用eval
的情况,各javascript引擎将按标准的方式进行处理,无视该间接调用eval
的存在。
同样的,对于new Function('return largeObject;')
这种情形,由于标准规定new Function
创建的函数的[[Scope]]
是全局的LexicalEnvironment,因此也无法访问到largeObject
,所有引擎都参照间接调用eval
的方式,选择无视Function
构造函数的调用。
多个嵌套函数
使用代码
function outer() {
var largeObject = LargeObject.fromSize('100MB');
function help() {
largeObject;
// eval('');
}
return function() {
debugger;
};
}
var inner = outer();
inner();
测试结果
- JScript.dll – 不回收,内存无下降趋势。
- Chakra – 不回收,内存无下降趋势。
- Carakan – 不回收,内存无下降趋势。
- V8 – 不回收,访问
largeObject
可得到正确的值。 - SpiderMonkey – 不回收,访问
largeObject
可得到正确的值。
结论
不仅仅是被返回的inner
函数,如果在outer
函数中定义的嵌套的help
函数中使用了largeObject
变量(或直接调用eval
),也同样会造成largeObject
变量无法回收。因此javascript引擎扫描的不仅仅是inner
函数的源码,同样扫描了其他所有嵌套函数的源码,以判断是否可以解除某个特定变量的绑定。
使用with
表达式
使用代码
function outer() {
var largeObject = LargeObject.fromSize('100MB');
var scope = { o: LargeObject.fromSize('100MB') };
with (scope) {
return function() {
debugger;
};
}
}
var inner = outer();
inner();
测试结果
- JScript.dll – 不回收,内存无下降趋势。
- Chakra – 回收
largeObject
,但不回收scope.o
,内存恢复至outer
函数被调用前并增加100MB左右(无法得知scope
是否被回收)。 - Carakan – 不回收,内存无下降趋势。
- V8 – 不回收,访问
largeObject
和scope
以及o
均可得到正确的值。 - SpiderMonkey – 回收
largeObject
和scope
,访问该2个变量均得到undefined
,不回收o
,可得到正确的值。
结论
当有with
表达式时,V8将会放弃所有变量的回收,保留LexicalEnvironment中所有变量的绑定。而SpiderMonkey则会保留由with
表达式生成的新的LexicalEnvironment中的所有变量的绑定,而对于outer
函数生成的LexicalEnvironment,按标准的方式进行处理,尽可能解除其中的变量绑定。
使用catch
表达式
使用代码
function outer() {
var largeObject = LargeObject.fromSize('100MB');
try {
throw { o: LargeObject.fromSize('100MB'); }
}
catch (ex) {
return function() {
debugger;
};
}
}
var inner = outer();
inner();
测试结果
- JScript.dll – 不回收,内存无下降趋势。
- Chakra – 回收
largeObject
和ex
,内存会恢复到outer
函数被调用前的状态。 - Carakan – 不回收,内存无下降趋势。
- V8 – 仅回收
largeObject
,访问largeObject
抛出ReferenceError,但仍可访问到ex
。 - SpiderMonkey – 仅回收
largeObject
,访问largeObject
得到undefined
,但仍可访问到ex
。
结论
catch
表达式虽然会增加一个LexicalEnvironment,但对闭包内变量的绑定解除算法几乎没有影响,这源于catch
生成的LexicalEnvironment仅仅是追加了被catch的Error对象一个绑定,是可控的(相对的with
则不可控),因此对变量回收的影响也可以控制和优化。但对于新生成并添加了Error对象的LexicalEnvironment,V8和SpiderMonkey均不会进一步优化回收,而Chakra则会对该LexicalEnvironment进行处理,如果其中的Error对象可以回收,则会解除其绑定。
嵌套函数中声明的同名变量
使用代码
function outer() {
var largeObject = LargeObject.fromSize('100MB');
return function(largeObject /* 或在函数体内声明 */) {
// var largeObject;
};
}
var inner = outer();
inner();
测试结果
- JScript.dll – 不回收,内存无下降趋势。
- Chakra – 回收,内存会恢复到
outer
函数被调用前的状态。 - Carakan – 不回收,内存无下降趋势。
- V8 – 回收,内存会恢复到
outer
函数被调用前的状态。 - SpiderMonkey – 回收,内存会恢复到
outer
函数被调用前的状态。
结论
嵌套函数中有与外层函数同名的变量或参数时,不会影响到外层函数中该变量的回收优化。即javascript引擎会排除FormalParameterList和所有VariableDeclaration表达式中的Identifier,再扫描所有Identifier来分析变量的可回收性。
总体结论
首先一个较为明确的结论是,以下内容会影响到闭包内变量的回收:
- 嵌套的函数中是否有使用该变量。
- 嵌套的函数中是否有直接调用
eval
。 - 是否使用了
with
表达式。
Chakra、V8和SpiderMonkey将受以上因素的影响,表现出不尽相同又较为相似的回收策略,而JScript.dll和Carakan则完全没有这方面的优化,会完整保留整个LexicalEnvironment中的所有变量绑定,造成一定的内存消耗。
由于对闭包内变量有回收优化策略的Chakra、V8和SpiderMonkey引擎的行为较为相似,因此可以总结如下,当返回一个函数fn
时:
-
如果
fn
的[[Scope]]
是ObjectEnvironment(with
表达式生成ObjectEnvironment,函数和catch
表达式生成DeclarativeEnvironment),则:- 如果是V8引擎,则退出全过程。
- 如果是SpiderMonkey,则处理该ObjectEnvironment的外层LexicalEnvironment。
-
获取当前LexicalEnvironment下的所有类型为Function的对象,对于每一个Function对象,分析其FunctionBody:
- 如果FunctionBody中含有直接调用eval,则退出全过程。
- 否则得到所有的Identifier。
- 对于每一个Identifier,设其为
name
,根据查找变量引用的规则,从LexicalEnvironment中找出名称为name
的绑定binding
。 - 对
binding
添加notSwap
属性,其值为true
。
-
检查当前LexicalEnvironment中的每一个变量绑定,如果该绑定有
notSwap
属性且值为true
,则:- 如果是V8引擎,删除该绑定。
- 如果是SpiderMonkey,将该绑定的值设为
undefined
,将删除notSwap
属性。
对于Chakra引擎,暂无法得知是按V8的模式还是按SpiderMonkey的模式进行。
从以上测试及结论来看,V8确实是一个优秀的javascript引擎,在这一方面的优化相当到位。而SpiderMonkey则采取一种更为友好的方式,不直接删除变量的绑定,而是将值赋为undefined
,也许是SpiderMonkey团队考虑到有一些极端特殊的情况,依旧有可能导致使用到该变量,因此保证至少不会抛出ReferenceError打断代码的执行。而IE9的Chakra相比IE8的JScript.dll进步非常大,细节上的处理也很优秀。Opera的Carakan在这一方面则相对落后,完全没有对闭包内的变量回收进行优化,选择了最为稳妥但略显浪费的方式。
此外,所有带有优化策略的浏览器,都在内在开销和速度之间选择了一个平衡点,这也正是为什么“多个嵌套函数”这一测试用例中,虽然inner
没有再使用largeObject
对象,甚至在inner
中的断点处,连help
函数对象也已经解除绑定,却没有解除largeObject
的绑定。基于这种现象,可以推测各引擎均只选择检查一层的关联性,即不去处理inner -> help -> largeObject
这样深度的引用关系,只找inner -> largeObject
和help -> largeObject
并做一个合集来处理,以提高效率。也许这种方式依旧存在内存开销的浪费,但同时CPU资源也是非常贵重的,如何掌握这之间的平衡,便是javascript引擎的选择。
此外,根据部分开发者的测试,Chakra甚至有资格被称为现有最快速的javascript引擎,微软也一直在努力,而开发者更不应该一味地谩骂和嘲笑IE。
我们可以嘲笑IE6的落后,可以看不到低版本的IE曾经为互联网的发展做过的贡献,可以在这些历史产品已经没落的今天无情地给予打击,却最最不应该将整个IE系列一视同仁,挂上“垃圾”的名号。客观地去看待,去评价,正是一个技术人员应该具备的最基本的准则和素养。
发表评论
-
Lo-Dash 与 underscore,Prototype 与 jQuery,两段恩怨情仇
2015-12-15 09:35 1736这几天更新我的之前写的 Chrome 插件 ChromeSn ... -
开发者必备的 Chrome 扩展
2014-11-03 15:01 2042Firebug:不用多介绍了吧https://chrome. ... -
开发者福利!ChromeSnifferPlus 插件正式登陆 Chrome Web Store
2014-10-30 21:14 106643今天(2014-10-30)下午,ChromeSniffer ... -
Firebase 相关文章索引
2014-10-23 10:21 2278Awesome Firebase 最近谷歌收购Fireb ... -
JSON API:用 JSON 构建 API 的标准指南中文版
2014-10-14 08:26 19875译文地址:https://github. ... -
前端开发,从菜鸟到大牛的取经之路
2014-07-14 15:15 378889以我的经验,大部分技术,熟读下列四类书籍即可。 入门, ... -
JavaScript Puzzlers 解密(一):为什么 ["1", "2", "3"].map(parseInt) 返回 [1, NaN, NaN]?
2014-02-19 10:58 40556JavaScript Puzzlers! 被称为 javas ... -
JavaScript 的怪癖 4:未知变量名创建全局变量
2013-12-02 15:25 1392原文:JavaScript quirk 4: unknown ... -
JavaScript 的怪癖 5:参数的处理
2013-12-02 15:23 3245原文:JavaScript quirk 5: parame ... -
【ghost初级教程】 怎么搭建一个免费的ghost博客
2013-10-28 14:10 6219ghost博客系统无疑是这个月最火热的话题之一,这个号称” ... -
10 个你需要了解的最佳 javascript 开发实践
2013-10-16 13:54 3608Javascript 的很多扩展的特性是的它变得更加的犀利 ... -
基于 canvas 将图片转化成字符画
2013-09-26 15:05 6591猛点 这里 看高清 ... -
详解一下 javascript 中的比较
2013-09-22 09:30 1929代码1: [] == []; // false ... -
谈 javascript 变量声明
2013-06-14 10:35 1900这篇文章还是对基础的复习,对面试经历的一个总结。 之前的 ... -
[译]Javascript 作用域和变量提升
2013-06-13 13:16 5216下面的程序是什么结果? var fo ... -
javascript:可以运行的噪音
2013-06-01 09:34 9304为我的博客做了一个很有 geek 风格的关于页面。运行下面 ... -
【layoutit!】基于 bootstrap 实现可视化布局工具
2013-05-20 11:23 14638Layout It 是一个在线工具,它可以简单而又快速搭建 ... -
回复:浮点数0.57 0.58 造出的坑爹问题
2013-05-09 11:30 25187今天看到 vb2005xu 提到了一个问题 浮点数0.5 ... -
javascript 中强制执行 toString()
2013-04-26 13:25 1551原文:Enforcing toString() 译文:ja ... -
JavaScript 中的“自动分号插入”机制(ASI)
2013-04-24 08:41 8448原文:Automatic semicolon insert ...
相关推荐
4. **内存管理**:闭包中的变量会持续存在于内存中,直到不再被任何闭包引用为止,这可能会导致内存泄漏问题。 ##### 1.3 理解闭包 为了更好地理解闭包的概念,我们可以通过一个简单的例子来说明: ```javascript...
如果外部函数对象被持有,其内部变量也不会被垃圾回收,即使这些变量在闭包中已经被赋值为null,但若外部函数仍然可访问,该变量仍不能被回收。 为了避免内存泄漏,可以采取以下措施: 1. 打破循环引用:确保没有...
这是因为闭包内部函数引用了这些变量,导致它们不会被垃圾回收器清理。 3. **内存管理**:闭包在内存管理上扮演着重要角色。由于它可以保留变量状态,因此在某些情况下可能导致内存泄漏。开发者需要注意避免无用的...
然而,当函数返回一个内部引用了外部变量的闭包时,即使外部作用域不再需要这些变量,它们也无法被垃圾回收,因为闭包仍然持有对它们的引用。这就是闭包引发内存泄露的一种常见方式。 内存问题主要分为以下几种类型...
如果不再需要闭包,应确保将其设置为 `null` 来释放引用,让垃圾回收器可以回收相关资源。 总之,闭包是JavaScript中一种强大的工具,它允许我们创建持久化的、私有的变量状态,实现模块化和数据封装。理解并熟练...
在闭包中,即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的作用域中的变量,这是因为这些变量并没有被垃圾回收机制回收,而是被闭包函数“持有”。 #### 四、闭包的应用场景 闭包具有多种应用场景,...
8. **垃圾回收与闭包**:Python的垃圾回收机制会考虑闭包的存在,只要闭包还被引用,它所访问的外部变量就不会被回收。 通过以上的知识点,你可以开始尝试解决“python高阶闭包练习题”中的问题。在实践中应用这些...
因为闭包会保持对外部函数执行上下文的引用,如果闭包没有被及时释放,可能会导致这部分内存无法被垃圾回收器回收。 2. **性能问题**:频繁地创建和维护闭包可能会带来额外的性能开销。 总之,正确理解和使用闭包...
因为闭包使得变量一直驻留在内存中,而不会被垃圾回收机制回收。 2. 过度使用闭包可能会造成性能问题,因为闭包会增加额外的内存消耗。 ### 闭包与循环 在循环中创建闭包可能会遇到问题。如果直接在循环内部创建...
这是因为`i`存在于闭包环境中,即使函数`a`已经执行完毕,`i`仍然被`b`函数所引用,因此不会被垃圾回收机制清除。这也就解释了为什么连续调用`c()`时,`i`的值会依次递增。 #### 闭包的应用场景 闭包在JavaScript...
JavaScript中的闭包是一种强大的特性,它允许函数访问和操作其外部作用域内的变量,即使在外部函数执行完毕后,闭包依然能保持对外部变量的访问。闭包的关键在于,它能够保留函数内部状态,使得数据得以持久化,这...
以下是一些关于JavaScript闭包的关键知识点: 1. **函数嵌套**:闭包最常见的形式是内部函数引用了外部函数的变量。例如: ```javascript function outerFunction() { var outerVar = 'I am from the outer ...
关于变量的问题,除了作用域和声明关键字外,还可能涉及变量提升、作用域链、闭包、全局变量、垃圾回收等概念。由于这些概念相对复杂,对于初学者来说,可能会出现理解上的困难。因此,建议通过阅读官方文档、查阅...
### 闭包作用域 #### 一、JavaScript闭包简介 ...通过理解闭包的作用域和生命周期,开发者可以更好地利用闭包来解决实际问题,并避免潜在的内存管理问题。掌握闭包的使用对于成为一名合格的前端工程师至关重要。
- 外部函数的变量不会在内部函数执行完毕后被垃圾回收。 - 内部函数可以继续访问外部函数的参数和变量,即使外部函数已经返回。 5. **闭包的应用场景**: - **数据封装**:闭包可以用来创建私有变量,防止全局...
1. **内存泄漏**:由于闭包的作用域链包括外部函数作用域,外部函数的变量即使在外部函数执行完毕后也不会被垃圾回收,除非所有闭包都不再使用这个变量。因此,不恰当的使用闭包可能导致内存泄漏。 2. **性能影响**...
- **持久化状态**:闭包可以保留函数内部的状态,即使函数执行完毕,其内部的变量也不会被垃圾回收机制回收,使得这些变量可以在后续调用中仍然保持其值。 2. **闭包的两种主要形式** - **函数作为返回值**:一个...
闭包的本质是函数能够访问并保留其定义时的作用域中的变量,即使该函数在其他作用域中被调用。这意味着,当函数执行时,它可以访问到在其外部定义但未在当前作用域中声明的变量。 闭包的特性使得它在JavaScript中...
- **内存管理**:由于闭包可以阻止变量被垃圾回收,因此可能会导致内存泄漏,但如果管理得当,可以避免这个问题。 - **封装私有变量**:通过闭包,可以在函数内部创建私有变量,外部无法直接访问。 3. **闭包的...