(这篇文章后面关于onclick事件的解释是错误的,请不要被误导了2016.6.16)

闭包这个概念给JavaScript初学者心中留下了巨大的阴影,网络上关于闭包的文章不可谓不多,但是能让初学者看懂的很少,所以这里我将用尽量浅显的语言来解释这个概念,以裨初学者参考。

闭包是什么?很简单,闭包就是可以访问其他函数作用域的中的变量的函数。那么什么函数可以访问其他函数中的私有变量呢?当然是在函数内部定义的函数可以访问父函数中的变量。所以理论上来讲,当我们在一个函数内部定义了一个函数的时候,这个子函数就可以叫做闭包了。例如:

function foo(){
var bar = 0;
function boo(){ //我是一个闭包
return ++bar;
}
console.log(f1());//
console.log(f1());//
console.log(f1());//
}

理论上来讲,这里的boo就是一个闭包。因为它可以访问其他函数作用域中的变量(bar),不过这样的闭包没有什么用,因为闭包没有出口,它会随着父函数的终结而终结,也就不会产生那些奇妙的效果。

我们来看下面的代码:

function foo(){
var bar = 0;
return function boo(){ //我是一个闭包
return ++bar;
}
}
var f1 = foo();
console.log(f1());//
console.log(f1());//
console.log(f1());//

这个例子中我们把子函数boo作为父函数的返回值输出到外面,这下它终于可以重见天日了。它可以在父函数死了以后继续存在,而不是被包在里面随着foo函数的终结而终结。我们给变量f1赋值为foo(),那么f1就是foo return回来的值,也就是一个函数。当赋值结束后,foo函数生命终止。而boo(被赋给f1)继续存在。这时候它仍然可以访问已经死去的父函数中的变量bar。这种形式就是我们最常见的闭包形式。

为什么boo还可以访问bar呢?因为当创建一个函数时,系统会自动为他创建一个作用域链,作用域链中保存着一系列的变量对象(变量对象:执行环境创建后,系统自动把执行环境中的所有变量打包成的对象),首先是由它自己的所有变量组成的变量对象,然后是它父函数的变量对象,一直到全局作用域。只要函数本身没有死亡,它的作用域链中存在的变量对象也永远不会被系统回收,即使那个变量对象对应的本身的函数已经死亡。这里变量bar是父函数中的变量,所以它会一直保存在子函数boo的作用域链里,因而boo就永远可以访问它。这也就是“闭包”这个名词的来源——即闭包执行时它的作用域链中打包了所有父环境中的变量,可以随时使用,不管父环境是否消亡。

当父函数死亡后,闭包脱离父环境单独在外面执行时,就会发生一些我们不想见到的状况(副作用)。看下面的代码:

function createArray(){
var result = new Array(); for(var i=0;i<10;i++){
result[i] = function(){
return i;
};
}
return result;
} var c1 = createArray();
console.log(c1[0]());//
console.log(c1[1]());//
console.log(c1[2]());//

我们期望每次得到的是0,1,2,3...,可实际上每次都得到10。我们来分析一下这段代码。在父函数内部,我们先定义了一个数组,然后通过for循环,把它赋值为一个函数(为什么不直接赋值为i呢,这可能是因为我们在赋值之前还想添加其他代码),这时候,result数组的每一个元素都是一个小闭包。当我们令c1 =createArray()时,我们得到十个小闭包。这时候,createArray已经死了。当这十个小闭包要引用i的时候,只能从它们的作用域链里找到一张照片(snapshot),也就是for最后一次执行后i的值也就是10,于是结果永远等于10。

怎么解决这个问题呢?请看下面的代码:

function createArray(){
var result = new Array(); for(var i=0;i<10;i++){
result[i] = (function(num){
return function(){
return num;
}
})(i);
}
return result;
} var c1 = createArray();
console.log(c1[0]());//0
console.log(c1[1]());//
console.log(c1[2]());//2

很多人遇到这样的代码就说这是在利用闭包来解决问题。实际上正好相反,这是在用立即执行的匿名函数来消除闭包的副作用。我们创建了一个立即执行的匿名函数,这个匿名函数是原来的数组赋值函数的父函数,这就相当于在赋值函数的作用域链里插入了新的变量对象,每次匿名函数执行的时候,立即把i的值给了num,因此小闭包的作用域链里就有了新的变量num,这样在外部执行的时候它就不会直接去找i了,而会先找到num,于是我们的问题就解决了。

再来一个栗子,当我们想给HTML中元素批量添加事件时,常常会发生只执行最后一个事件的状况,看下面的代码:

//给li元素批量添加click事件
window.onload = function(){
var lists = document.getElementsByTagName("li");
for(var i=0;i<lists.length;i++){
lists[i].onclick = function(){  //我是闭包
alert(i);
}
}
} //修改后的代码
window.onload = function(){
var lists = document.getElementsByTagName("li");
for(var i=0;i<lists.length;i++){
lists[i].onclick = (function(num){//我是匿名函数,我的作用是给闭包的作用域链中增加一个变量
return function(){
alert(num);
}
})(i);
}
}

这段代码中,onclick触发事件响应函数的时候,父函数(onload的响应函数)早已经死了,出现了我们熟悉的状况——子函数脱离父函数单独执行。这时子函数作用域链里面存i的只是最后一次for循环后的值,解决方法当然是创建匿名函数(作为事件响应函数的父函数)来马上接收每次循环i的值。

总结上面的内容发现,一个讽刺的事实是,很多时候你以为你在用闭包解决问题,实际上你只是在用匿名函数消除闭包的副作用而已。

那么,闭包的真正用武之地在哪里呢?答案是数据安全,实现私有变量。javascript中有句话叫,全局变量是魔鬼。为了防止命名冲突和恶意篡改,我们通常尽量不定义全局变量。看下面的代码:

var addSelf = (function(){
var count = 0; //我将会成为闭包里的私有变量
return function(){ //我是一个闭包
return count++;
}
})()

这段代码创建了一个自增器,父函数的作用域中定义了一个变量count,父函数是立即执行函数,执行完毕生命周期结束,返回一个自增器子函数,又一次,这个子函数脱离定义它的父函数环境执行,这个时候count仅仅存在于子函数的作用域链里,对于其他所有人都是不可见的,这就很好的保证了数据安全。

理解JavaScript中的闭包的更多相关文章

  1. 深入理解JavaScript中的闭包

    闭包没有想象的那么简单 闭包的概念在JavaScript中占据了十分重要的地位,有不少开发者分不清匿名函数和闭包的概念,把它们混为一谈,我希望借这篇文章能够让大家对闭包有一个清晰的认识. 大家都知道变 ...

  2. 【原】理解javascript中的闭包

    闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 什么是闭包? 官方说法: 闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见 ...

  3. 深入理解javascript中的闭包!(转)

    1.闭包的经典错误 假如页面上有若干个div,我们想给它每个绑定一个onclick方法,于是有了下面的代码. function A(){ var divs=document.getElementsBy ...

  4. 【原】理解javascript中的闭包(***********************************************)

    阅读目录 什么是闭包? 闭包的特性 闭包的作用: 闭包的代码示例 注意事项 总结 闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 回到顶 ...

  5. 全面理解JavaScript中的闭包的含义及用法

    1.什么是闭包 闭包:闭包就是能够读取其他函数内部变量的函数;闭包简单理解成“定义在一个函数内部的函数”. 闭包的形式:即内部函数能够使用它所在级别的外部函数的参数,属性或者内部函数等,并且能在包含它 ...

  6. 理解 JavaScript 中的 this

    前言 理解this是我们要深入理解 JavaScript 中必不可少的一个步骤,同时只有理解了 this,你才能更加清晰地写出与自己预期一致的 JavaScript 代码. 本文是这系列的第三篇,往期 ...

  7. [译]Javascript中的闭包(closures)

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  8. JavaScript中的闭包理解

    原创文章,转载请注明:JavaScript中的闭包理解  By Lucio.Yang 1.JavaScript闭包 在小学期开发项目的时候,用node.js开发了服务器,过程中遇到了node.js的第 ...

  9. 深入理解javascript原型和闭包 (转)

    该教程绕开了javascript的一些基本的语法知识,直接讲解javascript中最难理解的两个部分,也是和其他主流面向对象语言区别最大的两个部分--原型和闭包,当然,肯定少不了原型链和作用域链.帮 ...

随机推荐

  1. 「 Luogu P2230 」X 「 Vijos 1142 」 HXOS系统

    题目描述可能稍有偏差,但实质上是一样的. 看下面 题目大意 题面这么长,先说说题意吧. 就是有一个操作系统,他的存储方式是树形的.其中分为文件和目录(文件夹)每一个子目录下只能存储 $K$ 个文件或目 ...

  2. Gym - 101670B Pond Cascade(CTU Open Contest 2017 贪心,二分)

    题目: The cascade of water slides has been installed in the park recently and it has to be tested. The ...

  3. gnulpot

    gnulpot Table of Contents 1. Label position 2. coordinates 3. Symbols 4. key 4.1. key position 4.2. ...

  4. 类中的__call__()

    class A: def __call__(self, *args, **kwargs): print('执行了call方法') def call(self): print('执行call方法') c ...

  5. ajax加载本地html文件出现 XMLHttpRequest cannot load的问题

    谷歌浏览器ajax加载本地html文件出现 XMLHttpRequest cannot load的问题(火狐中不会出现这问题) Cross origin requests are only suppo ...

  6. Display PowerPoint slide show within a VB form or control window

    The example below shows how to use VB form/control as a container application to display a PowerPoin ...

  7. ZOJ 1654 Place the Robots

    题目大意: 在空地上放置尽可能多机器人,机器人朝上下左右4个方向发射子弹,子弹能穿过草地,但不能穿过墙, 两个机器人之间的子弹要保证互不干扰,求所能放置的机器人的最大个数 每个机器人所在的位置确定了, ...

  8. CF576D. Flights for Regular Customers

    n<=150个点,m<=150条路,每条路Ai,Bi,Di表示Ai到Bi有一条有向边,使用他前至少要走Di条路,问1到n最少走几条路. 又是n^4过150的题.... 不同于传统的最短路, ...

  9. CODEVS1022 覆盖 (二分图染色+匈牙利算法)

    先对整幅图进行二分图染色,再跑一遍匈牙利算法. /* CODEVS1022 */ #include <cstdio> #include <cstring> #include & ...

  10. RESTFUL 和SOA初探

    这篇文章是转载的,restful简单的说就是url明确的指向资源.soa还不好用自己的话解释,但明显不是这样,好吧,我自己的理解就是soa就是访问网站的一个接口.以访问一个blog list为例子,  ...