JS完美收官之——闭包

       在上一篇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函数就是好男人,可是它有个前提——就是被保存到外部的时候。痴情的人虽然颇有魅力,但请不要贪杯哦,它也有缺陷...... spa

闭包的弊端:当内部函数被保存到外部的时候必定生成闭包,闭包会致使原有的做用域链不释放形成内存泄漏。3d

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

闭包的小例子:blog

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个独一无二的当即执行函数,当即执行函数里面的函数分别保存了各自的当即执行函数的执行期上下文,因此当里面的函数被保存到外部执行的时候就会打印各自保存的值。

执行结果

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

「欢迎各位大佬关注个人微信公众号,扫二维码便可」