(这篇文章后面关于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. 「 HDU P2089 」 不要62

    和 HDOJ 3555 一样啊,只不过需要多判断个 ‘4’ 我有写 3555 直接去看那篇吧 这里只放代码 #include <iostream> #include <cstring ...

  2. for 循环新的写法==列表解析

    1. (for x in L1) 是一个可迭代对象: 2. 列表解析比for 循环快,列表解析的迭代在解释器内部是以C语言速度执行, 而不是手动python代码执行: (x+10 for x in L ...

  3. ssh中将常用的命令做别名

    1.vim ~/.bashrc 将光标落到user下面 2. 输入 alias x=‘ssh的命令’ 3.按ESC键,退出输入状态: 4.按:,然后输入wq,保存退出: 5. source ~/.ba ...

  4. [bzoj1500][NOI2005 维修数列] (splay区间操作)

    Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目. 第2行包含N个数字,描述初始时的数列. 以下M行,每 ...

  5. web项目的创建

    1) 创建Mave的webapp项目 2) 在Pom文件中添加servlet-api的依赖 <dependency> <groupId>javax.servlet</gr ...

  6. 【Codeforces 404C】Restore Graph

    [链接] 我是链接,点我呀:) [题意] 每个节点的度数不超过k 让你重构一个图 使得这个图满足 从某个点开始到其他点的最短路满足输入的要求 [题解] 把点按照dep的值分类 显然只能由dep到dep ...

  7. HDU 1220 简单数学题

    题目大意是 在魔方上找到有多少对小立方块它们之间连接的点不超过两个 因为任意两个立方块之间相连的点就只有0,1,2,4 这样4种情况 那么我们只需要考虑总共的组成立方块对数 sum = C(2 , n ...

  8. Linux下汇编语言学习笔记76 ---

    这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...

  9. 深入解析Microsoft Sql server 2008

    http://blog.csdn.net/downmoon/article/details/5256548

  10. golang中channels的本质详解,经典!

    原文:https://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html The Nature Of Channels In Go 这篇 ...