何为闭包?

在我的理解看来,闭包说白了就是一个函数,它可以引用函数外部的变量,当然这里的外部其实是一个作用域(也可以理解成为另一个函数)。如果这个作用域为函数作用域的话,即使外层函数已经执行完毕,但是里面被内层函数引用的变量依旧存储在内存中,除非内层函数已经执行完毕。

闭包原理

说到闭包,就不得聊一聊作用域的问题了,举个栗子:

  1. function testClosure(propertyName){
  2. return function(obj1,obj2){
  3. var p1=obj1['propertyName'];
  4. var p2=obj2['propertyName'];
  5. if(p1>p2){
  6. return 1;
  7. }else if(p1<p2){
  8. return -1;
  9. }else{
  10. return 0;
  11. }
  12. }
  13. }
  14. var result=testClosure('name');
  15. result({name:'mickey'},{name:'bob'});

ok,这其实就是一个简单的闭包,要特别注意匿名函数中的 var p1=obj1['propertyName']和var p2=obj2['propertyName'];仔细发现这里引用了外层函数的变量propertyName, 即使这个内部函数被返回了,而且是在被其他地方调用了,但他仍然可以访问变量propertyName,之所以可以访问这个变量,是因为内部函数的作用域链中包含testClosure()的作用域。关于作用域链,如下一张图:

fullsizerender(4).jpg

用闭包解决实际问题

案例 1

  1. function testClosure(){
  2. var result=new Array();
  3. for(var i=0;i<10;i++){
  4. result[i]=function(){
  5. return i;
  6. };
  7. }
  8. return result;
  9. }

如上,在执行testClosure函数的时候,result其实是一个函数数组,返回值是每项值为10的数组,而非想象中的那样返回值为1…10,为了解决以上问题,我们可以这样去实现

解决方案 1

  1. function testClosure(){
  2. var result=new Array();
  3. for(var i=0;i<10;i++){
  4. result[i]=function(num){
  5. return function(){
  6. return num;
  7. }
  8. }(i);
  9. }
  10. return result;
  11. }

如上解决方法是利用立刻执行函数和闭包的特性将每一次for循环的变量i传入自执行的匿名函数中,在自执行函数中,利用闭包的特性,返回一个函数,这个函数就是我们result数组中的函数,然后在函数中通过return一个num来实现返回一个值,这样,每一个for循环在进行迭代的时候,i的值都被保存在自执行函数中,即使自执行函数执行完就销毁,但是里面的变量由于被内部函数引用,所以会一直停留在内存中。

解决方案 2

  1. function testClosure(){
  2. var result=new Array();
  3. for(let i=0;i<10;i++){
  4. result[i]=function(){
  5. return i;
  6. };
  7. }
  8. return result;
  9. }

这里不仔细看的话,会发现和之前的源代码没有太大区别,但是在for循环里面,我们使用ES6中的let关键字取代了var,ES6中的let为JavaScript引入了块级作用域的概念,这就说明每一次for循环进行迭代的时候,i的值都只为每一个迭代中的{ }服务,相当于每一次迭代只维护一个{ },在C++或者JAVA中,如果直接在for循环里面声明i变量的话,它是默认具有块级作用域的。但是var不行,var即使在里面申请也会被默认看成是全局的(相对于一个函数作用域的全局)

案例 2

我们常常在为按钮绑定事件的时候,正常情况下,直接一个button.onclick=function(){ },其实就完事儿了,但是对于多个按钮呢?而且我还想实现点击的时候区别到底是哪一个按钮被触发了呢?如下:

  1. <body>
  2. <button class="button">按钮1</button>
  3. <button class="button">按钮2</button>
  4. <button class="button">按钮3</button>
  5. <script type="text/javascript">
  6. let buttons=document.getElementsByClassName("button");
  7. for(var i=0;i<buttons.length;i++){
  8. buttons[i].onclick=function(){
  9. console.log(i+1);
  10. };
  11. }
  12. </script>
  13. </body>

这里很遗憾的是,当我们点击任何一个按钮,控制台都只会输出5。

解决方案

  1. <body>
  2. <button class="button">按钮1</button>
  3. <button class="button">按钮2</button>
  4. <button class="button">按钮3</button>
  5. <script type="text/javascript">
  6. let buttons=document.getElementsByClassName("button");
  7. for(var i=0;i<buttons.length;i++){
  8. buttons[i].onclick=function(num){
  9. return function(){
  10. console.log(num+1); }
  11. }(i);
  12. }
  13. </script>
  14. </body>

原理和上个案例类似,不在过多赘述,第二种解决方案也是使用let替换var.

案例 3

详情请点击JS中的闭包理解