JavaScript闭包详解

  官方的解释:
 「闭包」,是指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。我的理解:闭包就是将函数内部和函数外部连接起来的一座桥梁。

理解闭包应该从三个方面入手,不像有的文章一句话就随之了了,让人摸不着头脑。

1、闭包让外部能够访问函数内部的属性。
 
这个是闭包的作用之一,我们知道函数内定义的私有属性,外部是无法访问的,有的人可能说函数直接return私有属性不就可以了吗?
这是可以的,但是有的私有属性不想叫外部知道,但是外部还需要用到私有属性, 这个时候就不能直接返回了。

比如:每个人的银行卡密码肯定只有自己知道,别人知道那就麻烦了。我们取钱的时候要求输入密码,输入密码的时候你很有可能看看周围有没有人, 因为你怕别人知道你的银行卡密码。
另一方面,输入密码是使用键盘输入的,而不是说出密码,试想一下如果取钱的时候要求 说出密码那就悲催了。 取款机必须知道银行卡的密码才能取钱,但是为了防止银行卡的密码被别人知道,就要求使用键盘输入 密码。
其实,可以把 键盘当做闭包里面返回的函数或者对象,因为它里面返回了我们输入的密码,同时,外部看不到我们输入的密码。

  2、 闭包返回的对象或者函数里面一定要使用内部变量

闭包是为了把内部变量通过一个函数或者对象间接的返回给外部使用,所以,这是刚需。不要认为在函数里面,再定义一个函数那就是闭包,那是函数嵌套。


function a(){
 funciton b(){

   }
}

这个不是闭包,是函数嵌套。


function a(){
   var i=1;
  function b(){
   }

  return b();
}

上面这个 也不是闭包,只是把函数b 作为了返回值,但是没有使用内部变量


function a(){
   var i=1;
  function b(){
      return i;
   }

  return b();
}

这个才是真正的 闭包。

3、闭包能够在内存中维持一个变量,防止被 垃圾回收 。


function a(){  
  var i=0;  
  function b(){  
    var j=i; //只要是引用了 i 的。  
     alert(j);  
  }  
  return b;  
}  
  
var c=a(); //a 执行完了,但由于' c();' 的原因 a 的内部变量不会被回收  
c(); //通过c 调用 b 来访问 i (维持 i)  

内存中维持一个变量另一方面是为了减少全部变量的使用,减少环境变量的污染,另一方面也可以减少参数的传递。

一些闭包应用场景

 

1、setTimeout需要传递参数

我们知道setTimeout是延迟执行多少时间之后再执行函数,一般函数是一个匿名函数,当然也可以是声明函数,例如:

setTimeout(function(){
  console.log('ok');
},5000)

也可以是:

   var ok=function(){
        console.log('ok');
    }
    setTimeout(ok, 5000);

上面两个方法都有一个问题那就是无法给函数传递参数,这个时候 如果需要一个变量大家一般会声明一个全局变量,然后在setTimeout里面使用,例如:

 var a="68kejian.com";
    setTimeout(function() {
        console.log(a);
    }, 5000);

    var ok=function(){
        console.log(a);
    }
    setTimeout(ok, 5000);

这种写法有一定的问题,那就是容易造成变量 污染。所以,这个时候我们就可以使用闭包了。我们看一下实例:

  function callLater(paramA) {
        /*使用函数表达式创建并放回一个匿名内部函数的引用*/
        return (function() {
            /*
            这个内部函数将被setTimeout函数执行;
            并且当它被执行时,
            它能够访问并操作外部函数传递过来的参数
            */
            console.log(paramA);
        });
    }
    /*
            调用这个函数将在它的执行上下文中创建,并最终返回内部函数对象的引用
            传递过来的参数,内部函数在最终被执行时,将使用外部函数的参数
            返回的引用被赋予了一个变量
            */
    var funcRef = callLater('68kejian.com');

    setTimeout(funcRef, 5000);

上面实例5秒之后打印:

68kejian.com

 

 

 


2、为节点循环绑定click事件,在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点


//假设有两个a链接,id分别为"hello"、"world"
    function test(obj) {
        return function() {
            alert(obj.id);
        }
    }
    var nodes = document.getElementsByTagName("a");
    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i]
        nodes[i].attachEvent("onclick", function() {
            alert(node.id)
        });
        //点击链接时,hello链接弹出world,world链接也弹出world;
        //这是因为循环完毕后,node被赋值为world元素
        //这不是我们预期的结果!!!

        //正确写法一 // 内部参数parm为任意指定的参数,
        //如:(function(node){return function(){alert(node.id)}})(node)
        nodes[i].attachEvent("onclick", (function(parm) {
            return function() {
                alert(parm.id)
            }
        })(node));
        //第一次循环创建了一个闭包,缓存的node参数为hello链接。
        //第二次循环又创建了一个闭包,缓存的node参数为world链接。
        //点击链接时,hello链接弹出hello,world链接弹出world,因为他们调用的是各自的node参数

        //正确写法二
        var func = test(node);
        nodes[i].attachEvent("onclick", func)
    }

3、暂停执行

这个可以做很多实用和有意思的交互。

//自执行先打印1和2 然后点击继续打印3和4
<input type="button" value="继续" onclick='st();'/> <script type="text/javascript"><!-- var st = (function() { alert(1); alert(2); return function() { alert(3); alert(4); } })(); // --></script> 把这个作用延伸下,我想到了用他来实现window.confirm。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>react画廊</title>
<link rel="stylesheet" href="css/style.css">
<style>
body { font-family: Arial;
font-size: 13px;
background-color: #FFFFFF;
}

#confirmDiv { width: 200px;
height: 100px;
border: dashed 1px black;
position: absolute;
left: 200px; top: 150px;
}
</style>
</head>
<body>

<div>
<input name="btn2" type="button" value="删除" onclick="doConfirm('confirmDiv');" />
<div id="confirmDiv" style="display: none;">
<div style='position: absolute; left: 50px; top: 15px;'>
<p>
你确定要删除吗?</p>
<input type="button" value="确定" onclick="doConfirm('confirmDiv')(true);" />
<input type="button" value="取消" onclick="doConfirm('confirmDiv')(false);" />
</div>
</div>
</div>

<script>
var $ = function(id) { return "string" == typeof id ? document.getElementById(id) : id; }
var doConfirm = function(divId) {
$(divId).style.display = "";
function closeDiv() { $(divId).style.display = "none"; }
return function(isOk) {
if (isOk) { alert("Do deleting..."); }
closeDiv();
} }
</script>
</body>
</html>

 


4、循环绑定事件的时候闭包也很有用,关于闭包有个很经典的例子示例:让这每个li节点的onclick事件都能正确的弹出当前被点击的li索引

 <ul >
        <li> index = 0</li>
        <li> index = 1</li>
        <li> index = 2</li>
        <li> index = 3</li>
    </ul>
<script type="text/javascript">

    var nodes = document.getElementsByTagName("li");
    for(i = 0;i<nodes.length;i+= 1){
        nodes[i].onclick = function(){
            alert(i);//值全是4
        };
    }
</script>
闭包实现:

      var handlers = function(elements){
        var ref = function(i){
            return function(e){
                alert(i);
            };
      };

        var i;
        for(i = 0;i<elements.length;i+= 1){
            elements[i].onclick = ref(i);
        }
    };
    handlers(document.getElementsByTagName("li"));

 

 
posted @ 2016-11-01 10:19  codingSoul  阅读(371)  评论(0)    收藏  举报