JS完美收官之——闭包

javascript/jquery

浏览数:5

2020-5-23

AD:资源代下载服务

       在上一篇JS完美收官之作用域中,我们已经知道当函数执行完毕后,它所产生的执行期上下文会被销毁,被世人称之为渣男类型的,用完就丢掉,而今天我们探究的是闭包却与之相反,可以将闭包理解为”痴情的男孩”,就是不管怎么打,怎么骂,都紧紧拽着你衣角的那种,不由想起曾小贤那句“好男人就是我,我就是闭包”。好了,我们一起来看看到底什么是闭包?为何被称为好男人呢?

 直接上代码:

function a(){
    function b(){
        var bbb = 234;
        console.log(aaa);
     }
     var aaa = 123;
     return b;
}
var glob = 100;
var demo = a();
demo();

当a函数被定义的时候会生成全局的执行期上下文GO(global object)放在作用域链的第0位,紧接着在a函数执行的前一刻会生成局部的执行期上下文AO(activation object)放在作用域链最顶端(第0位是最顶端,1是次顶端,查找顺序是从最顶端往下查)。看图:

然而在上面代码中a函数的执行产生b函数的定义,并且b函数被return保存了出来,b函数的定义是站在a函数的劳动成果之上,所以看图:

当a函数执行完毕之后会销毁自己的执行期上下文,咣当一下把自身作用域链上那条线剪断了,a销毁后回到被定义的状态等待下次被执行,但并不代表a函数里面的执行期上下文跟着消失了,因为b还存着a函数执行期上下文的引用,紧紧拽着它呢。b被保存全局demo中,当执行demo()的时候,会生成一个独一无二的执行期上下文放到作用域链的最顶端,随后执行console.log(aaa),由于自身上没有aaa,那么它会顺着作用域链自上而下一次查找,此时作用域链上绑着三条引用,如图:

以上过程其实就是闭包,但凡内部函数被保存到了外部,它一定生成闭包闭包是JavaScript最强大的特性之一,因为它允许函数可以访问除局部作用域之外的数据。

同时也解释下文章开头说的”痴男”,这里的b函数就是好男人,但是它有个前提——就是被保存到外部的时候。痴情的人虽然很有魅力,但请不要贪杯哦,它也有缺陷……

闭包的弊端:当内部函数被保存到外部的时候一定生成闭包,闭包会导致原有的作用域链不释放造成内存泄漏。

那啥叫作用域链不释放啊,本来a函数执行完毕后要销毁自身的执行期上下文从而羽化登仙,但是由于b函数死活拽着a函数的脚丫子不让走,时间久了,天干物燥,b函数身体水分蒸发过快,造成缺水。(水分从毛孔蒸发的过程就是内存泄漏,内存用的越多剩的越少,就像泄漏了一样),要是b函数牛劲犯了,拽着好几百个死活不放手,就会导致系统空间过多被占用,会影响执行速度,在脚本编程中,一定要非常小心地使用闭包,因为它同时关系到内存和执行速度,我们通常把跨作用域变量存储在局部变量中,然后直接访问局部变量。以此来减轻闭包对执行速度的影响。

闭包的小例子:

1.实现公有变量(写个不依赖外部变量的累加器)

function add(){
    var count = 0;
    function demo(){
        count ++;
        console.log(count);
    }
    return demo; 
}
var counter = add();
counter();       
counter();

执行结果:

我们可以用闭包来做一个不依赖外部变量的累加器,调用多少次就会加多少次,这样的好处可以把局部变量变成私有状态,减少了全局变量的使用,全局变量处在作用域链的最底层,位置越深执行速度就会越缓慢,具体慢多少还得取决于浏览器,所以我们在写程序的时候尽量使用局部变量

全局变量还有一个非常重要的问题:那就是会发生命名冲突,比如说第三方库里面定义了一个全局变量global,然后又在函数里面定义了一个全局变量global,这样就会出现问题,后面的global覆盖前面那个global,那第三方库可能就失效了。在下一篇文章中,我们可以一起探讨下最小化全局变量的方法,比如说立即执行函数、命名空间的模式、用var声明变量…….

2.写一个打印0~9数字

依旧先看代码:

function test(){
    var arr = [];
     for(var i = 0;i<10; i++){
        arr[i] = function (){
            console.log(i);
        }
    }
    return arr;
}
var myArr = test();
for(var j = 0;j<10; j++){
    myArr[j]();
}

执行结果

看到这结果是不是跟我们想的不一样!那为什么会打印10个10呢?第一点我们要注意的是执行语句并不是一定义就执行的,console.log(i)里面的i的值不是立即打印的,而是要等被保存到外部函数的执行才打印,这段代码创建了10个闭包,并将它们存储在一个数组中,数组中的10个函数分别与test函数形成闭包并且共享test函数生成的执行期上下文,也就是共享变量 i ,当test()返回时,变量 i 的值为10,所以闭包都共享这个值,因此数组中的函数的返回值都是同一个值。

那有什么办法可以解决这个问题吗?我们就是想让它打印0~9,该如何处理呢?

我们可以用立即执行函数解决,看如下代码:

function test() {
        var arr = [];
            for (var i = 0; i < 10; i++) {
                (function (j) {
                    arr[j] = function () {
                        console.log(j);
                    }
                }(i))
            }
            return arr;
        }
        var myArr = test();
        for (var j = 0; j < 10; j++) {
            myArr[j](); 
        }

加了立即执行函数之后,可以实现的的效果就是让 i 执行到第6行的时候立即打印出来 ,把 i 当做实参传给形参 j ,当下面代码

 arr[j] = function () {
           console.log(j);
    }

被保存到外部时,拿到的是立即执行函数的所产生的执行期上下文,与立即执行函数形成闭包,由于在for循环中,会产生10个独一无二的立即执行函数,立即执行函数里面的函数分别保存了各自的立即执行函数的执行期上下文,所以当里面的函数被保存到外部执行的时候就会打印各自保存的值。

执行结果

『 好啦,以上呢,就是这期给大家的分享啦,谢谢支持~』

「欢迎各位大佬关注我的微信公众号,扫二维码即可」

作者:空白