Javascript闭包的一些研究
本文不谈闭包的概念,因为概念容易把人搞晕,本文希望通过几个鲜活的例子来探究闭包的性质,相信对理解闭包会有所帮助。
程序1
var f = (function() {
var n = 10;
return function() {
++n;
console.log(n);
}
})();
f();
输出:
11
结论:
闭包函数可以访问外层函数中的变量。
程序2
var arr = [];
(function() {
var n = 0;
for (var i=0; i<3; ++i) {
arr[i] = function() {
++n;
console.log(n);
}
}
})(); for (var i=0; i<3; ++i) {
var f = arr[i];
f();
}
输出:
1
2
3
结论:
一个函数内有多个闭包函数,那么这些闭包函数共享外层函数中的变量。可以看出,例子中3个闭包函数中的n是同一个变量,而不是该变量的3个副本。
程序3
var f0 = function() {
++n;
console.log(n);
}
var f = (function() {
var n = 10;
return f0;
})();
f();
输出:

错误指向“++n”这一行。
结论:
闭包函数的作用域不是在引用或运行它的时候确定的,看起来好像是在定义它的时候确定的。
说明:
虽然该程序与“程序1”看起来一样,但由于函数f0一个在内部定义一个在外部定义,尽管它们都是从函数内部返回,但在这个例子中f0却无法访问变量n。
程序4
var f = (function() {
var n = 10;
return new Function('++n;console.log(n);');
})();
f();
输出同“程序3”:

结论:
该程序是对“程序3”的进一步印证和补充。由该程序可以得出的结论是:闭包函数的作用域是在编译它的时候确定的。
说明:
使用Function构造器可以创建一个function对象,函数体使用一个字符串指定,它可以像普通函数一样执行,并在首次执行时编译。因此,虽然在匿名函数内部定义了此function对象,但一直要到调用它的时候才会编译,即执行到“f()”时,而此时原函数的作用域已经不存在了。
再看一个例子:
程序5
var f = (function() {
var n = 10;
return eval('(function(){++n;console.log(n);})');
})();
f();
输出:
11
结论:无
说明:
这个例子是对上面两个程序的补充。这个例子之所以能够和“程序1”一样打印出11,是因为eval( )函数会立即对传递给它字符串进行解析(编译、执行),因此使用eval定义的函数和直接定义的效果是等价的。
(注意:eval( )中的“function(){...}”必须用括号扩起来,否则会报错)
程序6
var f = (function() {
var n = 10;
var s = 'hello';
return function() {
++n;
console.log(n);
}
})();
f();
运行时在“console.log(n);”这一行加个断点,查看作用域中的值,其中只有n没有s:

结论:
外层函数中那些未在闭包函数中使用的变量,对闭包函数是不可见的。在这个例子中,闭包函数没有引用过变量s,因此其作用域中只有n。也就是说,对闭包函数来说,其可以访问的外层函数的变量实际上只是真正的外层函数变量的一个子集。
程序7
这个程序用来通过数据证明“程序6”的结论。程序稍微有点复杂,后面会先对其做简单说明。
var funArr = [];
var LENGTH = 500;
var ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; function getStr( ) {
var s = '';
for (var i=0; i<LENGTH; ++i) {
s += ALPHABET[Math.floor(Math.random( ) * 62)];
}
return s;
} function getArr( ) {
var a = new Array(LENGTH);
for (var i=0; i<LENGTH; ++i) {
a[i] = Math.random( );
}
} var f = function( ) {
var n = 10;
var s = getStr( );
var a = getArr( ); funArr.push(function( ) {
console.log(n, s, a); // --- 1
console.log(n); // --- 2
})
} for (var i=0; i<2000; ++i) {
f( );
}
程序分析:
本程序的重点是for循环和函数f。for循环中调用了函数f 2000次,每次调用都会创建一个数字和两个长度为500的字符串和数组,所以2000次函数调用所创建的局部变量的规模还是比较可观的,程序用这种方法以便于后面做分析时对结果进行比较。
f中的局部变量会被一个闭包函数所引用,以此观察未被引用的局部变量是否会被回收。
分别运行该程序两次,第一次使用语句1(引用了f中的所有局部变量),第二次使用语句2(只引用了数字变量n)。对运行所得到的结果1和结果2分别采集堆快照(Heap Snapshot):

可以看到所占内存差别巨大,从这里就可以初步得出“未被闭包函数引用的局部变量会被回收”的结论。
不过为了严谨性,需要做更细致地分析。首先是结果1和结果2的统计图:


可以看到,第二次运行后内存中的对象数量明显要比第一次的少很多(二者产生的中间对象数量是相同的),这进一步说明了第二次运行后大部分对象都被回收了。
最后我们将结果2与结果1进行细致的比较,结果如下:

结论:
函数中的局部变量如果没有被任何闭包函数所引用(这里不考虑被全局变量所引用的情况),则这些局部变量在函数运行完成后就可以被回收,否则,这些变量会作为其闭包函数的作用域的一部分被保留,直到所有闭包函数也执行完毕为止。
该结论同时也印证了“程序6”的结论:闭包函数对外层函数作用域的引用是外层函数真实作用域的一个子集。
另外从这个实验还能推测出一点,即外层函数执行结束后是会被回收掉的。因为既然函数内部变量已被回收,那函数本身也没有存在的意义了。
Javascript闭包的一些研究的更多相关文章
- JavaScript闭包的底层运行机制
转自:http://blog.leapoahead.com/2015/09/15/js-closure/ 我研究JavaScript闭包(closure)已经有一段时间了.我之前只是学会了如何使用它们 ...
- 什么是JavaScript闭包终极全解之一——基础概念
本文转自:http://www.cnblogs.com/richaaaard/p/4755021.html 什么是JavaScript闭包终极全解之一——基础概念 “闭包是JavaScript的一大谜 ...
- JavaScript 闭包环境非常奇特 - 相当于类与实例的关系?!
JavaScript 闭包环境非常奇特 - 相当于类与实例的关系?! 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一 ...
- 我也谈javascript闭包的原理理解
参考原文:http://www.oschina.net/question/28_41112 前言:还是一篇入门文章.Javascript中有几个非常重要的语言特性——对象.原型继承.闭包.其中闭包 对 ...
- Javascript 闭包与高阶函数 ( 二 )
在上一篇 Javascript 闭包与高阶函数 ( 一 )中介绍了两个闭包的作用. 两位大佬留言指点,下来我会再研究闭包的实现原理和Javascript 函数式编程 . 今天接到头条 HR 的邮件,真 ...
- JavaScript闭包,只学这篇就够了
# 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义. 其实只要理解了核心概念,闭包并不是那么的难于理解.但是,网上充斥了太多学术性的 ...
- JavaScript闭包只学这篇就够了
闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义. 其实只要理解了核心概念,闭包并不是那么的难于理解.但是,网上充斥了太多学术性的文章 ...
- 《Web 前端面试指南》1、JavaScript 闭包深入浅出
闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...
- JavaScript 闭包深入浅出
闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...
随机推荐
- sql取整函数
SQL取整运算2009年04一个月02日本 星期四 10:01有使用说明这种方法记录,就在今天,那么当仍然被遗忘.事实上通常用四舍五入的操作有几种情况,一个是简单的四舍五入,无论是小数点后面的是什么都 ...
- String.format()【演示具体的例子来说明】
String.format()[演示样例具体解释] 整理者:Vashon 前言: String.format 作为文本处理工具.为我们提供强大而丰富的字符串格式化功能,为了不止步于简单调用 Strin ...
- POJ9384 迷宫(基金会BFS)
本文来源于:http://blog.csdn.net/svitter 称号:让你从(0, 0)走到(4,4).而且输出路径. 输入数据:二位数组的迷宫:输出数据:路径: 题解:简单的BFS 注意: 1 ...
- oracle 打开trace,并分析trace
SQL> oradebug event 10046 trace name context forever,level 8 ORA-00072: process "Unix proces ...
- 如何从Terminal Command Line编译并运行Scope
Ubuntu SDK我们大部分的开发者是非常有效的.它甚至可以帮助我们进行在线调试.在这篇文章中,我们介绍了如何使用command line编译和执行我们scope. 1)创建一个主Scope 我们能 ...
- 移动端 微信 网易 触屏滑动回弹菜单(css版)
总结一下: 有点:网易新闻,微信 热文 都是 -webkit-overflow-scrolling: touch;实现,css实现,轻巧: bug: 部分安卓浏览器(uc,自带) 只支持持续滑动,不 ...
- nginx 1.4.2 安装笔记
顺便吐槽一下,有道笔记的排版太难用了, 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊 ========================================== ...
- spring整合JMS - 基于ActiveMQ实现
一. 开篇语 继上一篇apache ActiveMQ之初体验后, 由于近期一直在复习spring的东西, 所以本文就使用spring整合下JMS. 二. 环境准备 1. ActiveMQ5.2.0 ( ...
- jvm在存储区域
当区域执行的数据 JVM存储器的管理分为几个时间之后的数据区的实施:程序计数器.JavaVM栈.本地方法栈.Java堆.方法区(包括常量池的实现). 程序计数器 较小的内存空间,能够看作是当前线 ...
- spring交易声明的几个传播特性
近期遇到了一个spring事务导致的问题,所以写了几个小程序了解了一下事务的传播特性,以下分别举样例分别看看事务的传播特性. 事务的几种传播特性 1. PROPAGATION_REQUIRED: 假设 ...