首页 / JAVA / 理解JavaScript中的闭包
理解JavaScript中的闭包
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了理解JavaScript中的闭包,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含3236字,纯文字阅读大概需要5分钟。
内容图文
闭包这个概念给JavaScript初学者心中留下了巨大的阴影,很多人学到闭包的时候半途而废。这里我将尽量用人类的语言来解释这个概念。
闭包是什么?很简单,闭包就是可以访问其他函数作用域的中的变量的函数。那么什么函数可以访问其他函数中的私有变量呢?当然是在函数内部定义的函数可以访问父函数中的变量。所以理论上来讲,当我们在一个函数内部定义了一个函数的时候,这个子函数就可以叫做闭包了。例如:
function foo(){ var bar = 0; function boo(){ //我是一个闭包return ++bar; } console.log(f1());//1 console.log(f1());//2 console.log(f1());//3 }
理论上来讲,这里的boo就是一个闭包。因为它可以访问其他函数作用域中的变量(bar),不过这样的闭包没有什么用,因为闭包没有出口,它会随着父函数的终结而终结,也就不会产生那些奇妙的效果。
我们来看下面的代码:
function foo(){ var bar = 0; returnfunction boo(){ //我是一个闭包return ++bar; } } var f1 = foo(); console.log(f1());//1 console.log(f1());//2 console.log(f1());//3
这个例子中我们把子函数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]());//10 console.log(c1[1]());//10 console.log(c1[2]());//10
我们期望每次得到的是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){ returnfunction(){ return num; } })(i); } return result; } var c1 = createArray(); console.log(c1[0]());//10 console.log(c1[1]());//10 console.log(c1[2]());//10
很多人遇到这样的代码就说这是在利用闭包来解决问题。实际上正好相反,这是在用立即执行的匿名函数来消除闭包的副作用。我们创建了一个立即执行的匿名函数,这个匿名函数是原来的数组赋值函数的父函数,这就相当于在赋值函数的作用域链里插入了新的变量对象,每次匿名函数执行的时候,立即把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){//我是匿名函数,我的作用是给闭包的作用域链中增加一个变量 returnfunction(){ alert(num); } })(i); } }
这段代码中,onclick触发事件响应函数的时候,父函数(onload的响应函数)早已经死了,出现了我们熟悉的状况——子函数脱离父函数单独执行。这时子函数作用域链里面存i的只是最后一次for循环后的值,解决方法当然是创建匿名函数(作为事件响应函数的父函数)来马上接收每次循环i的值。
总结上面的内容发现,一个讽刺的事实是,很多时候你以为你在用闭包解决问题,实际上你只是在用匿名函数消除闭包的副作用而已。
那么,闭包的真正用武之地在哪里呢?答案是数据安全,实现私有变量。javascript中有句话叫,全局变量是魔鬼。为了防止命名冲突和恶意篡改,我们通常尽量不定义全局变量。看下面的代码:
var addSelf = (function(){ var count = 0; //我将会成为闭包里的私有变量returnfunction(){ //我是一个闭包return count++; } })()
这段代码创建了一个自增器,父函数的作用域中定义了一个变量count,父函数是立即执行函数,执行完毕生命周期结束,返回一个自增器子函数,又一次,这个子函数脱离定义它的父函数环境执行,这个时候count仅仅存在于子函数的作用域链里,对于其他所有人都是不可见的,这就很好的保证了数据安全。
原文:http://www.cnblogs.com/cyniczzz/p/4855188.html
内容总结
以上是互联网集市为您收集整理的理解JavaScript中的闭包全部内容,希望文章能够帮你解决理解JavaScript中的闭包所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。