JavaScript闭包详解

闭包是JavaScript一个精髓和难点所在,很久以前就开始了解它,总想写点什么,遗憾的能力有限,包括您现在看到的这篇文章,大多数也是来自其他优秀的书籍和少部分自己的理解,我将只充当一个搬运工的角色,好了不多说了,开始正题吧!

在了解闭包之前我们必须要先去深入了解JavaScript的作用域和作用域链。在JavaScript中作用域是在函数定义的时候决定的,而不是函数调用时决定的,举例说明。

       var count=5;
            function test1(){
                var count =0;
                 function getCount(){
                    return count;
                 }
                 return getCount();
            } 
console.log(test1());//0

在上面的代码中我们最后得到的结果应该是0,原因很简单因为在执行getCount这个函数时,返回的会是局部变量count(这个可以去看一下JavaScript变量的查询规则)。那么再看下面这个例子。

            var count=5;
            function test1(){
                var count =0;
                 function getCount(){
                    return count;
                }
                
                return getCount;    
            }
            console.log(test1()());//0

在这段代码中,我们只做了一点改变,我们返回的是一个函数,而不是函数的执行结果,所以最后要变成test1()()。那么大家可以看到执行的结果依然是0。这就说明了函数的作用域是在函数定义时就已经确定了,而不是在函数调用时确定的。因为如果是在调用时确定,那么最后执行test1()()这个函数,实际上是执行getCount()这个函数,而这个函数中会反回一个count,在当前的位置,能够拿到的count应该是全局变量count=5,打印结果应该是5,但是最后证明是0,所以就证明了上面那句话:在JavaScript中作用域是在函数定义的时候决定的,而不是函数调用时决定的。这就是闭包,在作用域外依然能够访问到局部变量,并且一直保持下去而不被垃圾回收(一般情况下,局部变量会在函数执行完后,被当做垃圾回收,而在全局域是不可能访问到这个局部变量的)。

再举一个例子:

       function test4(){
                var count=3;
                return {
                    getCount:function(){
                        return count;
                    }
                }
            }
            console.log(test4().getCount());//3

我们在执行test4().getCount();这个语句时,执行环境是在全局域中,按理说我们是不可能拿到局部变量中的count值的,但是我们拿到了,这就是闭包的神奇之处,大家可能已经看到了,闭包中一定会return一个东西,可以直接是这个局部变量,也可以是获取这个局部变量的方法。那么闭包有什么用呢,其实我们可以用来保护某些变量,要想获取或者改变这个变量就必须通过我给定的方法来实现,有点类似于Java里面的setter和getter方法,下面我们来模拟一下。

           function test5(){
                var count=6;
                return {
                    getCount:function(){
                        return count;
                    },
                    setCount:function(val){
                        return count=val;
                    }
                }
            }
            
            console.log(test5().getCount());  //6
            console.log(test5().setCount(9)); //9
            

以上就是模拟的getter和setter方法,一般情况下我们可能去定义一个全局变量来达到这种效果,但是大家都知道这样很不安全,而这种方法就很好的保证了count的安全性,不会被轻易的篡改,因为你不通过setCount这个方法你根本碰不了它。

特殊的例子,可能从里开始接触闭包开始就一定会碰到这个例子

           function test2(){
                var arr=[];
                for(var i=0;i<10;i++){
                    arr[i]=function(){
                        return i;
                    }
                }
                return arr;
            }
            
            console.log(test2()[5]());//10

上面的代码打印的结果是10,我们期待的结果应该是5。我们的本意应该是这个数组中,依次装入了0~9,但是最后我们发现,这个数组中装的全是10,为什么会这样呢。

当我们在执行arr[i]=function(){ return i;}时,并不是把i装入了数组中,而是把function(){return i;}这个函数装入了数组中,而这个函数并未有执行,怎么可能会return i呢!

而当我们最后执行test2()[5]()这句话时本意是要取出数组的第六个元素,在这时我们才会执行function(){return i;}这个函数(把test2()[5]替换成function(){return i;});而此时的i早已经变成了10,说以test2()[0]~test2()[9]的值都会是10,如果要达到我们的本意应该做如下改动:

            function test3(){
                var arr=[];
                for(var i=0;i<10;i++){
                    arr[i]=(function(){
                        return i;
                    }())
                }
                return arr;
            }
            console.log(test2()[7]);//7

我们运用一个及时函数,使每次function(){return i;}这个匿名函数立马执行掉,那么arr[i]装入的值就是对应的i值了(关于立即执行函数,大家可以去查看其它资料,也是JavaScript中相当重要的部分)。类似的例子还有。

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

我们的本意是想在1s以后依次打印0~9这10个数字,而真实结果却是全打印的10。原因依然是function(){console.log(i);}这个匿名函数要在1s以后才会执行,不是立马执行,而到1S以后,i的值早已经变为了10,解决办法如下:

          for(var i = 0; i < 10; i++) {
                (function(e) {
                    setTimeout(function() {
                        console.log(e);  
                    }, 1000);
                })(i);
            }

那么这样就会打印出0~9这10个数了。参见资料(JavaScript私密花园)。

好了关于闭包目前就只能写这么多了,大家可以看看《JavaScript权威指南》《你不知道的JavaScript》和《JavaScript语言精粹》中对闭包的解释,看了之后应该有个比较好的理解。

原文地址:https://www.cnblogs.com/djlxs/p/5407374.html