理解JavaScript中的闭包
(这篇文章后面关于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中的闭包的更多相关文章
- 深入理解JavaScript中的闭包
闭包没有想象的那么简单 闭包的概念在JavaScript中占据了十分重要的地位,有不少开发者分不清匿名函数和闭包的概念,把它们混为一谈,我希望借这篇文章能够让大家对闭包有一个清晰的认识. 大家都知道变 ...
- 【原】理解javascript中的闭包
闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 什么是闭包? 官方说法: 闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见 ...
- 深入理解javascript中的闭包!(转)
1.闭包的经典错误 假如页面上有若干个div,我们想给它每个绑定一个onclick方法,于是有了下面的代码. function A(){ var divs=document.getElementsBy ...
- 【原】理解javascript中的闭包(***********************************************)
阅读目录 什么是闭包? 闭包的特性 闭包的作用: 闭包的代码示例 注意事项 总结 闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 回到顶 ...
- 全面理解JavaScript中的闭包的含义及用法
1.什么是闭包 闭包:闭包就是能够读取其他函数内部变量的函数;闭包简单理解成“定义在一个函数内部的函数”. 闭包的形式:即内部函数能够使用它所在级别的外部函数的参数,属性或者内部函数等,并且能在包含它 ...
- 理解 JavaScript 中的 this
前言 理解this是我们要深入理解 JavaScript 中必不可少的一个步骤,同时只有理解了 this,你才能更加清晰地写出与自己预期一致的 JavaScript 代码. 本文是这系列的第三篇,往期 ...
- [译]Javascript中的闭包(closures)
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- JavaScript中的闭包理解
原创文章,转载请注明:JavaScript中的闭包理解 By Lucio.Yang 1.JavaScript闭包 在小学期开发项目的时候,用node.js开发了服务器,过程中遇到了node.js的第 ...
- 深入理解javascript原型和闭包 (转)
该教程绕开了javascript的一些基本的语法知识,直接讲解javascript中最难理解的两个部分,也是和其他主流面向对象语言区别最大的两个部分--原型和闭包,当然,肯定少不了原型链和作用域链.帮 ...
随机推荐
- C++ Primer(第4版)-学习笔记-第5部分:高级主题
第17章 用于大型程序的工具 异常处理 不存在数组或函数类型的异常.相反,如果抛出一个数组,被抛出的对象转换为指向数组首元素的指针,类似地,如果抛出一个函数,函数被转换为指向该函数的指针. 不要抛出 ...
- THUSC2019滚粗记
关于\(\mathrm{APIO}\)游记,它咕了... Day -1 \(\mathrm{\_tham}\)今天并没有准备给我们考试,所以机房充斥着过年的气息(雾 下午就要出发了,由于一些众所周知的 ...
- ios 7以后 隐藏顶部状态栏
iOS 7 以后,之前隐藏顶部状态栏的方法都已经失效.以下介绍的方法是全部兼容的. 修改xode工程里的 Info.plist 增加 Status bar is initially hidden一行, ...
- 从“菜鸟”码农到“资深”架构师,我到底经历了什么?--------http://baijiahao.baidu.com/s?id=1585813883835208757&wfr=spider&for=pc
http://baijiahao.baidu.com/s?id=1585813883835208757&wfr=spider&for=pc
- MVC系统学习4—ModelMetaData
在Mvc R2中,新引入了一些扩展方法,如后面带一个for的方法,这些扩展方法会根据Model的属性自定生成相应的Html元素,如Html.EditFor(Model=>Model.IsAppr ...
- jQuery通过event获取点击事件的事件对象
要想搞明白js的事件机制,必须搞清楚几个概念:事件对象,事件源,还有事件流 事件对象: 当事件发生时会产生事件对象,事件对象的作用是用来记录“事件发生是一些相关的信息.注意事件对象只有在事件发生时才会 ...
- BZOJ(1) 1003 [ZJOI2006]物流运输
1003: [ZJOI2006]物流运输 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 9404 Solved: 4087[Submit][Stat ...
- JAVA学习第四十一课 — 泛型的基本应用(一)
泛型是JDK1.5以后出现的安全机制,简化机制,提高安全性 泛型的长处 1.将执行时出现的问题ClassCastException转移到了编译时期 2.避免了强制转换的麻烦 <>在当操作的 ...
- 一起talk C栗子吧(第一百二十三回:C语言实例--显示变量和函数的地址)
各位看官们,大家好,上一回中咱们说的是多线程的样例.这一回咱们说的样例是:显示变量和函数的地址. 闲话休提,言归正转.让我们一起talk C栗子吧! 在编敲代码时,有时候须要获取程序中变量和函数的地址 ...
- Android Studio 开发利器【经常使用插件】
1.ADB Idea ADB操作快捷菜单.高速清除数据,重新启动应用,卸载应用等 快捷键: Windows: Ctrl+Alt+Shift+A Mac OSX: Ctrl+Shift+A 当然,你能够 ...