用JavaScript一年多了,闭包总是让人二丈和尚摸不着头脑。陆陆续续接触了一些闭包的知识,也犯过几次因为不理解闭包导致的错误,一年多了 资料也看了一些,但还是不是非常明白,最近偶然看了一下 jQuery基础教程 的附录,发现附录A对JavaScript的闭包的介绍简单易懂,于是借花献佛总结一下。

1.简单的例子

首先从一个经典错误谈起,页面上有若干个div, 我们想给它们绑定一个onclick方法,于是有了下面的代码

<div id="divTest">
<span>0</span> <span>1</span> <span>2</span> <span>3</span>
</div>
<div id="divTest2">
<span>0</span> <span>1</span> <span>2</span> <span>3</span>
</div>
$(document).ready(function() {
var spans = $("#divTest span");
for (var i = 0; i < spans.length; i++) {
spans[i].onclick = function() {
alert(i);
}
}
});

很简单的功能可是却偏偏出错了,每次alert出的值都是4,简单的修改就好使了

var spans2 = $("#divTest2 span");
$(document).ready(function() {
for (var i = 0; i < spans2.length; i++) {
(function(num) {
spans2[i].onclick = function() {
alert(num);
}
})(i);
}
});

2.内部函数

让我们从一些基础的知识谈起,首先了解一下内部函数。内部函数就是定义在另一个函数中的函数。例如:

function outerFn () {
functioninnerFn () {}
}

innerFn就是一个被包在outerFn作用域中的内部函数。这意味着,在outerFn内部调用innerFn是有效的,而在outerFn外部调用innerFn则是无效的。下面代码会导致一个JavaScript错误:

function outerFn() {
document.write("Outer function<br/>");
function innerFn() {
document.write("Inner function<br/>");
}
}
innerFn();

不过在outerFn内部调用innerFn,则可以成功运行:

function outerFn() {
document.write("Outer function<br/>");
function innerFn() {
document.write("Inner function<br/>");
}
innerFn();
}
outerFn();

2.1伟大的逃脱

JavaScript允许开发人员像传递任何类型的数据一样传递函数,也就是说,JavaScript中的内部函数能够逃脱定义他们的外部函数。

逃脱的方式有很多种,例如可以将内部函数指定给一个全局变量:

var globalVar;
function outerFn() {
document.write("Outer function<br/>");
function innerFn() {
document.write("Inner function<br/>");
}
globalVar = innerFn;
}
outerFn();
globalVar();

调用outerFn时会修改全局变量globalVar,这时候它的引用变为innerFn,此后调用globalVar和调用innerFn一 样。这时在outerFn外部直接调用innerFn仍然会导致错误,这是因为内部函数虽然通过把引用保存在全局变量中实现了逃脱,但这个函数的名字依然 只存在于outerFn的作用域中。

也可以通过在父函数的返回值来获得内部函数引用

function outerFn() {
document.write("Outer function<br/>");
function innerFn() {
document.write("Inner function<br/>");
}
return innerFn;
}
var fnRef = outerFn();
fnRef();

这里并没有在outerFn内部修改全局变量,而是从outerFn中返回了一个对innerFn的引用。通过调用outerFn能够获得这个引用,而且这个引用可以可以保存在变量中。

这种即使离开函数作用域的情况下仍然能够通过引用调用内部函数的事实,意味着只要存在调用内部函数的可能,JavaScript就需要保留被引用的函数。而且JavaScript运行时需要跟踪引用这个内部函数的所有变量,直到最后一个变量废弃,JavaScript的垃圾收集器才能释放相应的内存空间(红色部分是理解闭包的关键)。

说了半天总算和闭包有关系了,闭包是指有权限访问另一个函数作用域的变量的函数,创建闭包的常见方式就是在一个函数内部创建另一个函数,就是我们上面说的内部函数,所以刚才说的不是废话,也是闭包相关的 ^_^

1.2变量的作用域

内部函数也可以有自己的变量,这些变量都被限制在内部函数的作用域中:

function outerFn() {
document.write("Outer function<br/>");
function innerFn() {
var innerVar = 0;
innerVar++;
document.write("Inner function\t");
document.write("innerVar = "+innerVar+"<br/>");
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();

每当通过引用或其它方式调用这个内部函数时,就会创建一个新的innerVar变量,然后加1,最后显示

Outer function
Inner function innerVar = 1
Inner function innerVar = 1
Outer function
Inner function innerVar = 1
Inner function innerVar = 1

内部函数也可以像其他函数一样引用全局变量:

var globalVar = 0;
function outerFn() {
document.write("Outer function<br/>");
function innerFn() {
globalVar++;
document.write("Inner function\t");
document.write("globalVar = " + globalVar + "<br/>");
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();

现在每次调用内部函数都会持续地递增这个全局变量的值:

Outer function
Inner function globalVar = 1
Inner function globalVar = 2
Outer function
Inner function globalVar = 3
Inner function globalVar = 4

但是如果这个变量是父函数的局部变量又会怎样呢?因为内部函数会引用到父函数的作用域(有兴趣可以了解一下作用域链和活动对象的知识),内部函数也可以引用到这些变量

function outerFn() {
var outerVar = 0;
document.write("Outer function<br/>");
function innerFn() {
outerVar++;
document.write("Inner function\t");
document.write("outerVar = " + outerVar + "<br/>");
}
return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();

这一次结果非常有意思,也许或出乎我们的意料

Outer function
Inner function outerVar = 1
Inner function outerVar = 2
Outer function
Inner function outerVar = 1
Inner function outerVar = 2

我们看到的是前面两种情况合成的效果,通过每个引用调用innerFn都会独立的递增outerVar。也就是说第二次调用outerFn没有继续 沿用outerVar的值,而是在第二次函数调用的作用域创建并绑定了一个一个新的outerVar实例,两个计数器完全无关。

当内部函数在定义它的作用域的外部被引用时,就创建了该内部函数的一个闭包。这种情况下我们称既不是内部函数局部变量,也不是其参数的变量为自由变 量,称外部函数的调用环境为封闭闭包的环境。从本质上讲,如果内部函数引用了位于外部函数中的变量,相当于授权该变量能够被延迟使用。因此,当外部函数调 用完成后,这些变量的内存不会被释放(最后的值会保存),闭包仍然需要使用它们。

3.闭包之间的交互

当存在多个内部函数时,很可能出现意料之外的闭包。我们定义一个递增函数,这个函数的增量为2

function outerFn() {
var outerVar = 0;
document.write("Outer function<br/>");
function innerFn1() {
outerVar++;
document.write("Inner function 1\t");
document.write("outerVar = " + outerVar + "<br/>");
} function innerFn2() {
outerVar += 2;
document.write("Inner function 2\t");
document.write("outerVar = " + outerVar + "<br/>");
}
return { "fn1": innerFn1, "fn2": innerFn2 };
}
var fnRef = outerFn();
fnRef.fn1();
fnRef.fn2();
fnRef.fn1();
var fnRef2 = outerFn();
fnRef2.fn1();
fnRef2.fn2();
fnRef2.fn1();

我们映射返回两个内部函数的引用,可以通过返回的引用调用任一个内部函数,结果:

Outer function
Inner function 1 outerVar = 1
Inner function 2 outerVar = 3
Inner function 1 outerVar = 4
Outer function
Inner function 1 outerVar = 1
Inner function 2 outerVar = 3
Inner function 1 outerVar = 4

innerFn1和innerFn2引用了同一个局部变量,因此他们共享一个封闭环境。当innerFn1为outerVar递增一时,久违innerFn2设置了outerVar的新的起点值,反之亦然。我们也看到对outerFn的后续调用还会创建这些闭包的新实例,同时也会创建新的封闭环境,本质上是创建了一个新对象,自由变量就是这个对象的实例变量,而闭包就是这个对象的实例方法,而且这些变量也是私有的,因为不能在封装它们的作用域外部直接引用这些变量,从而确保了了面向对象数据的专有性。

3.解惑

现在我们可以回头看看开头写的例子就很容易明白为什么第一种写法每次都会alert 4了。

for (var i = 0; i < spans.length; i++) {
spans[i].onclick = function() {
alert(i);
}
}

上面代码在页面加载后就会执行,当i的值为4的时候,判断条件不成立,for循环执行完毕,但是因为每个span的onclick方法这时候为内部 函数,所以i被闭包引用,内存不能被销毁,i的值会一直保持4,直到程序改变它或者所有的onclick函数销毁(主动把函数赋为null或者页面卸载) 时才会被回收。这样每次我们点击span的时候,onclick函数会查找i的值(作用域链是引用方式),一查等于4,然后就alert给我们了。而第二 种方式是使用了一个立即执行的函数又创建了一层闭包,函数声明放在括号内就变成了表达式,后面再加上括号括号就是调用了,这时候把i当参数传入,函数立即 执行,num保存每次i的值。

这一通下来想必大家也和我一样,对闭包有所了解了吧,当然完全了解的话需要把函数的执行环境和作用域链搞清楚 ^_^

from:http://www.cnblogs.com/dolphinX/archive/2012/09/29/2708763.html

JavaScript 闭包究竟是什么的更多相关文章

  1. JavaScript 闭包究竟是什么JavaScript 闭包究竟是什么

    用JavaScript一年多了,闭包总是让人二丈和尚摸不着头脑.陆陆续续接触了一些闭包的知识,也犯过几次因为不理解闭包导致的错误,一年多了资料也看了一些,但还是不是非常明白,最近偶然看了一下 jQue ...

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

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

  3. JavaScript闭包其一:闭包概论 函数式编程中一些基本定义

    http://www.nowamagic.net/librarys/veda/detail/1707前面介绍了作用域链和变量对象,现在再讲闭包就容易理解了.闭包其实大家都已经谈烂了.尽管如此,这里还是 ...

  4. javascript闭包具体解释

    今天我们从内存结构上来解说下 javascript中的闭包概念. 闭包:是指有权訪问另外一个函数作用域中的变量的函数. 创建闭包的常见方式就是在一个函数内部创建另外一个函数. 在javascript中 ...

  5. [JavaScript闭包]Javascript闭包的判别,作用和示例

    闭包是JavaScript最重要的特性之一,也是全栈/前端/JS面试的考点. 那闭包究竟该如何理解呢? 如果不爱看文字,喜欢看视频.那本文配套讲解视频已发送到B站上供大家参考学习. 如果觉得有所收获, ...

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

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

  7. JavaScript 闭包深入浅出

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

  8. JavaScript闭包(Closure)

    JavaScript闭包(Closure) 本文收集了多本书里对JavaScript闭包(Closure)的解释,或许会对理解闭包有一定帮助. <你不知道的JavsScript> Java ...

  9. Javascript闭包和C#匿名函数对比分析

    C#中引入匿名函数,多少都是受到Javascript的闭包语法和面向函数编程语言的影响.人们发现,在表达式中直接编写函数代码是一种普遍存在的需求,这种语法将比那种必须在某个特定地方定义函数的方式灵活和 ...

随机推荐

  1. cassandra高级操作之JMX操作

    需求场景 项目中有这么个需求:统计集群中各个节点的数据量存储大小,不是记录数. 一开始有点无头绪,后面查看cassandra官方文档看到Monitoring章节,里面说到:Cassandra中的指标使 ...

  2. Android系统结构

    从上图中可以看出,Android系统架构为四层结构,从上层到下层分别是应用程序层.应用程序框架层.系统运行库层以及Linux内核层,分别介绍如下: (1)Appliacation Android平台不 ...

  3. Python javascript操作DOM

    文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口.它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式.我们最为关心的是,DOM把 ...

  4. 打造比Dictionary还要快2倍以上的字查找类

    针对一个长度为n的数组. [1,2,3,4,5,6,7,8,9] 最快的通用查找类是Dictionary其采用hashcode算法,复杂度为O(1). 而上大学时,最快的查找法为二分查找法,复杂度为O ...

  5. 事务隔离级别与传播机制,spring+mybatis+atomikos实现分布式事务管理

    1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). 原子性(Atomicity):即事务是不可分割的最小工作单 ...

  6. ios 渐进淡出

    在github上寻找的经典demo //以view为继承类 LazyFadeInView-master https://github.com/itouch2/LazyFadeInView //以Lab ...

  7. 20144306《网络对抗》CAL_MSF基础运用

    1  实验内容 一个主动攻击,如ms08_067 一个针对浏览器的攻击,如ms11_050 一个针对客户端的攻击,如Adobe 成功应用任何一个辅助模块 2  实验过程记录 2.1 主动攻击MS08- ...

  8. [Oracle]审计Audit

    1.Audit的概念 Audit是监视和记录用户对数据库进行的操作,以供DBA进行问题分析.利用Audit功能,可以完成以下任务: 监视和收集特定数据库活动的数据.例如管理员能够审计哪些表被更新,在某 ...

  9. lua 数组

    lua 数组 语法结构 arr = { - } 一维数组 数组的值仍然是数组的, 为多维数组, 否则为一维数组 示例程序 local arr = {1, 2, 3} for i = 1, #arr d ...

  10. keil5之32环境配置

    终于配置好了!!又是经过一下午加晚上的奋战,终于把环境配置好了,多亏了我强大的资料整理能力(哈哈). 真是不容易啊,本来打算放弃的,去问问别人吧.但是想想,还是靠自己吧,靠谁都不如靠自己,真是的,慢慢 ...