我们先来看一下闭包的概念:
当内部函数在定义它的作用域 的外部引用时,
就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,
当外部函数调用完毕后,这些变量在内存不会被释放,因为闭包需要用到它们.
官方的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式
(通常是一个函数),因而这些变量也是该表达式的一部分。
更通俗一些的理解:
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
闭包的作用:或者说为什么要使用闭包?
闭包的存在使得在函数外面可以访问函数内的局部变量,创建闭包的常见方式就是在一个函数a内部创建另一个函数b,并且返回这个函数b,外部变量就可以通过函数b来访问函数a里的局部变量了.
//a函数的定义: function a(){ var user="ZDL"; //直接返回一个匿名函数 return function(){ return user; } } //或者这样定义a函数 function a(){ var user="ZDL"; function b(){return user;} //返回具名函数(有名字的函数) return b; } //再或者 function a(){ var user="ZDL"; //把函数赋给一个变量,再返回这个变量 var b=function(){return user;} return b; } //这几个定义方式的效果都是一样的,都是在a函数的内部返回了一个函数 console.log(a()()); // ZDL var fn=a(); // b保存了A函数里的返回的匿名函数 console.log("另一种方式调用:",fn()); //另一种方式调用: ZDL
在这个例子中,我们在函数外部成功的访问到了函数a内的局部变量user,
而正常情况,在函数的外部是无法访问函数内的局部变量的,这是由于JS的作用域链的
缘故,关于js中的作用域链,看这篇文章,详解JS中的作用域
而闭包的存在,使得a的整个执行环境保留了下来,里面的变量的内存也都保留了下来
这就是闭包的一个优点,也可以说是它的缺点:就是可以把局部变量驻留在内存中,可以避免使
用全局变量。(全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,
所以推荐使用私有的,封装的局部变量)。
下面我们用闭包实现一些功能:
1. 实现一个局部变量的累加
function Test(){ var age=100; return function(){ age++; return age; } } var box=Test();//取得Test里的匿名函数 box(); //第一次调用匿名函数 101 box(); //第二次调用匿名函数 102 box(); //第三次调用匿名函数 103 console.log(box()); //104 //这里正是由于闭包的存在,使得函数Test里的局部变量age一直保存在内存中, //大量使用闭包可能会导致性能下降,建议在非常有必要的时候才使用闭包
2.
作用域链的机制导致一个问题,在循环中里的匿名函数取得的任何变量都是循环后的最后一个值
问题描述:
问题1:我想定义一个函数数组,数组里每一个函数的功能是打印出这个函数在函数中的索引。
类似的
问题2:实现给ul下的所有li添加一个事件,使得点击li时,弹出自己在ul中的索引。即点击
第一个li弹出0,第二个li弹出1,第n个li弹出n-1;
这几个是常见的面试题,经常会问到,我们有必要熟练掌握它。
情况1:
function box(){ var arr=[]; for(var i=0;i<5;i++){ arr[i]=function(){ return i; } } return arr; } var b=box(); console.log("数组长度:",b.length); for(var i=0;i<b.length;i++){ console.log(b[i]()); }
这里的例子输出的结果都是5,也就是循环结束后得到的i的值。因为b[i]调用的是匿
名函数,匿名函数并没有自我执行,等到调用的时候,box()已执行完毕,i 早已变成5,
同时每个函数访问到的都是同一个i,也就是循环结束后,值变为5的那个i。
使用闭包,写法1:
function box(){ var arr=[]; for(var i=0;i<5;i++){ arr[i]=(function(n){ return function(){ return n; } })(i); } return arr; } var b=box(); console.log("数组长度:",b.length); for(var i=0;i<b.length;i++){ console.log(b[i]()); } //闭包的写法2: function box(){ var arr=[]; for(var i=0;i<5;i++){ (function(n){ arr[i]=function(){ return n; } })(i); } return arr; } var b=box(); console.log("数组长度:",b.length); for(var i=0;i<b.length;i++){ console.log(b[i]()); }
这里通过闭包,自执行匿名函数的执行环境保存了下来,
每一个函数使用的都有自己的一个n,也就是当时自执行匿名函数传过来的i
接下来再来看看给ul下的所有li添加一个事件,点击各自实现出自己在ul中的索引:
html部分:
<ul id="ul1">
<li>我是第1个Li</li>
<li>我是第2个Li</li>
<li>我是第3个Li</li>
<li>我是第4个Li</li>
<li>我是第5个Li</li>
</ul>
我们第一想到的代码:
JS代码:
var aLis=document.getElementById("ul1").getElementsByTagName("li"); for(var i=0;i<aLis.length;i++){ aLis[i].onclick=function(){ alert(i); } }
这里的结果想必大家都知道了,点击每一个Li弹出的都是同一个值,都是5
也就是循环结束后,i的值,其实,由于作用域链的原因(关于 作用域链,可以看这篇文章) 详解JS中的作用域
每一个Li函数里访问到的都是同一个i,也就是循环结束后的那个i的。
解决办法:也是通过闭包来解决,这里直接附上代码:
写法1:
for(var i=0;i<aLis.length;i++){ aLis[i].onclick=(function(n){ return function(){ alert(n); } })(i); }
这里,每一个循环里都会执行一个自执行匿名函数,传入i,同时返回一个匿名函数
这相当于:
aLis[i].onclick=function(){ alert(n); }
// 当点击Li时,执行alert(n),这个n是在哪里找到的呢? 就是在上面它对应索引的那个匿名函数
找到的n,也就是传进来的i, 这个外面的自执行匿名函数相当于我们前面说到的函数a,里面返回的
那个匿名函数相当于函数b,函数b里用到了a函数的n,而b函数又被a函数外面的aLis[i]引用了
这就产生了闭包,而每执行一次循环,就会产生一个闭包,当执行Li的点击事件时,n就会在它们
各自对应的闭包里找到n,也就是找到了我们想到的索引值.
写法2:
for(var i=0;i<aLis.length;i++){ (function(n){ aLis[i].onclick=function(){ alert(n); } })(i); }
同样的,这样写和上面的原理是差不多的
写了这么多,也不知道你看懂了没有,第一次写这么长的文章,不容易呐
另一篇文章:详解JS中的作用域
转载请注明:副业 and 脱单研究所 » JS中的闭包二三事-2