原文: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闭包的一些研究的更多相关文章

  1. JavaScript闭包的底层运行机制

    转自:http://blog.leapoahead.com/2015/09/15/js-closure/ 我研究JavaScript闭包(closure)已经有一段时间了.我之前只是学会了如何使用它们 ...

  2. 什么是JavaScript闭包终极全解之一——基础概念

    本文转自:http://www.cnblogs.com/richaaaard/p/4755021.html 什么是JavaScript闭包终极全解之一——基础概念 “闭包是JavaScript的一大谜 ...

  3. JavaScript 闭包环境非常奇特 - 相当于类与实例的关系?!

    JavaScript 闭包环境非常奇特 - 相当于类与实例的关系?! 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一 ...

  4. 我也谈javascript闭包的原理理解

    参考原文:http://www.oschina.net/question/28_41112 前言:还是一篇入门文章.Javascript中有几个非常重要的语言特性——对象.原型继承.闭包.其中闭包 对 ...

  5. Javascript 闭包与高阶函数 ( 二 )

    在上一篇 Javascript 闭包与高阶函数 ( 一 )中介绍了两个闭包的作用. 两位大佬留言指点,下来我会再研究闭包的实现原理和Javascript 函数式编程 . 今天接到头条 HR 的邮件,真 ...

  6. JavaScript闭包,只学这篇就够了

    # 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义. 其实只要理解了核心概念,闭包并不是那么的难于理解.但是,网上充斥了太多学术性的 ...

  7. JavaScript闭包只学这篇就够了

    闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义. 其实只要理解了核心概念,闭包并不是那么的难于理解.但是,网上充斥了太多学术性的文章 ...

  8. 《Web 前端面试指南》1、JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

  9. JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

随机推荐

  1. java 加载dll介绍(转)

    最近在做的工作要用到本地方法,需要在Java中加载不少动态链接库(以下为方便延用Windows平台下的简写dll,但并不局限于Windows).刚刚把程序跑通,赶紧把一些心得写出来,mark.也希望对 ...

  2. jQuery 有条件排序

    尊重劳动成果,转载请注明出处(http://blog.csdn.net/sllailcp/article/details/41011173)... 点击button,表格里就会依照分数的高低.将学生信 ...

  3. C++指针和引用简介

    摘要 本文介绍C++指针和概念引用 这是一个指针 指针的类型 指针所指向的类型 指针表达式 指针与函数 什么是引用 指针引用差别 指针和引用的同样点和不同点 **什么是指针** 指针就是一个存放地址的 ...

  4. 第十六章——处理锁、阻塞和死锁(3)——使用SQLServer Profiler侦测死锁

    原文:第十六章--处理锁.阻塞和死锁(3)--使用SQLServer Profiler侦测死锁 前言: 作为DBA,可能经常会遇到有同事或者客户反映经常发生死锁,影响了系统的使用.此时,你需要尽快侦测 ...

  5. [DEEP LEARNING An MIT Press book in preparation]Linear algebra

    线性代数是数学的一个重要分支,它经常被施加到project问题,要了解学习和工作深入研究的深度,因此,对于线性代数的深刻理解是非常重要的.下面是我总结的距离DL book性代数中抽取出来的比較有意思的 ...

  6. Activity生命周期解决(有图有真相)

    Activity完整的生命周期: 启动Activity的周期历程: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcGVuZ2t2/font/5a6L5L2 ...

  7. JSP_include指令和&lt;jsp:include&gt;

    包括三个文件:jsp_include.jsp, static.html, two.jsp 周边环境:tomcat7.0. myeclipse10 1.jsp_include.jsp <%@ pa ...

  8. 它们的定义TextView使之具有跑马灯的效果

    一.引入问题 使用通用textview快乐效应,焦点事件不启动滚动,button目前的焦点事件,但丑,因此,需要定制TextView 天生焦点 个textview FocusedTextView.ja ...

  9. 怎么样CSDN Blog投机和增加流量?

    所谓推测装置,以提高它们的可见性,最近比较顾得上,这样一来打字游戏.一方面,练习打字速度 .在又一个方面中,以了解诱导的理论 版权声明:本文博客原创文章,博客,未经同意,不得转载.

  10. python 3.4.0 简单的print &#39;hello world&#39;,出错--SyntaxError: invalid syntax

    问题描写叙述: win7下安装的python 3.4.0版本号, 在命令行里写入简单的输出语句: print 'hello world' 然后enter,结果返回结果为: SyntaxError: i ...