深入理解闭包(六)——闭包

原文地址:深入理解闭包(六)——闭包

终于讲到闭包了,这一路走来不容易。
从前面的博文中我们知道,js的垃圾回收机制会在某个函数的执行上下文生命周期结束后将其回收,释放内存,但是闭包的存在会阻止这一过程。

概念

js高程对闭包的定义是:闭包是指有权访问另一个函数作用域中的变量的函数。 创建闭包的常见方式就是在一个函数内部创建另一个函数,作为返回值或参数传递到函数外部。但是根据我的经验,只要在一个函数内部使用了关键字function,一个闭包就被创建了。

实例

任何书面上的解释都不如一个实例来的有效。

        var a = 1;
        function test() {
            var b = 2;
            return function () {
                var c = 3;
                console.log(a+b+c);
            }
        }
        var result=test();
        result();   //6

按照我们之前的说法,函数外部是不能访问函数内部的变量的,代码执行到var result=test()这句时调用test函数,执行完毕后应该销毁执行上下文,其中的变量都不能再访问,但是执行result()时却仍然调用了test中的变量,这是因为我们在test函数中返回了一个匿名函数。
test函数中返回的不仅仅是一个函数,还有它的执行上下文环境,将其赋值给全局变量result后,那么result其实就等同于那个返回的匿名函数,当它被调用时可以访问匿名函数作用域链中指向的变量对象(并不是把变量对象复制给了result,只是可以被引用),这个返回的匿名函数就是闭包。
引用阮一峰老师对闭包的解释:闭包本质上是将函数内部和外部连接起来的桥梁

再看一个栗子:

    function out() {
        var a = 1;
        var inner=function () {
            console.log(a);
        }
        a++;
        return inner;
    }
    var b = out();
    b();        //2

肯定会有人觉得输出的应该是1,想着a++在console.log(a)后面,这是一种常见的错觉,代码创建的位置和执行的顺序是不一样的,这点要谨记。这段代码里首先执行的是调用out函数,out函数执行代码,最后一步返回inner,要知道a++是在return之前执行的,a=2已经保存到了变量对象中,接下来调用b函数(等同于调用inner函数),输出的a自然就是2.

复杂一点的栗子:

    var fun1,fun2,fun3;
    function test() {
        var a=10;
        fun1=function () {console.log(a);};
        fun2=function () {a++;};
        fun3=function (x){a=x;};
    }
    
    test();
    fun2();
    fun1();   //11
    fun3(5);
    fun1();   //5
    var fun4=fun1;
    test();
    fun1();   //10
    fun4();   //5

如果上一个栗子你已经理解,那么理解这个栗子前两次输出的11和5也不是什么难事,但后面的两个输出10和5你可能会有点疑惑。

  • 我们第二次调用test函数时一个新的闭包被创建,它能访问到的变量也是重新创建的,跟前面的没有关系,因此再调用fun1时输出10。我们要记住这句话:如果你在一个函数内部声明了另一个函数,那么这个外部函数每次被调用都会产生一个闭包,创建崭新的执行上下文环境。
  • 那么调用fun4怎么又输出了5呢,这是因为var fun4=fun1是在第一次调用test发生的,那么fun4可以访问的变量也是第一次调用test时创建的变量对象,即使在别的地方被调用,它的作用域链也就是可访问的变量是不变的。我们要记住这句话:一个函数可以访问的变量对象要到创建这个函数的执行环境中去找而不是调用这个函数的执行环境。

还有一个非常常见的栗子,那就是闭包对循环的影响:

    var arr=[];
    function test(){
        for (i= 0;i<3;i=i+1){
            arr[i]=function(){
                console.log(i);
            };
        }
    }
    test();
    arr[0](); // 3
    arr[1](); // 3
    arr[2](); // 3

函数运行之后会得到一个函数数组,你本想让每个函数都返回自己的索引值,比如运行arr[0]()时得到0,运行arr[1]()时得到1。但实际上,每个函数都输出3。这是因为闭包只能取得包含函数中任何变量的最后一个值,每个函数的作用域链中都保存着test函数中的变量对象,所以它们引用的是同一个变量i,而i的最后一个值是3,因此每个函数都输出3。我们可以通过创建一个自执行匿名函数来让闭包符合预期:

    var arr=[];
    function test(){
        for (i= 0;i<3;i=i+1){
            arr[i]=(function(num){
                console.log(num);
            })(i);
        }
    }
    test();   //0,1,2

此处用到了自执行匿名函数(function(){···})(),它的写法是,在函数体外面加一对圆括号,形成一个表达式,在圆括号后面再加一个圆括号,里面可传入参数。由于函数参数是按值传递的,所以就可以把变量i的当前值赋值给匿名函数的参数num,而自执行匿名函数可以不用调用,自己执行自己,因此只要检测到参数i就会立即执行并把结果传递给arr数组。这样一来我们就能得到预期的结果了。

缺点

最后一个栗子告诉我们,闭包有时也会给我们的工作带来负面效果。除此之外,还有两个缺点我们需要注意一下。

  1. 内存泄漏: 在ie9之前的版本中,如果闭包的作用域链中保存着一个html元素,那么就意味着该元素将无法销毁。
  2. 占用内存:由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以我们最好只在绝对必要时再考虑使用闭包。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,702评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,143评论 3 415
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,553评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,620评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,416评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,940评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,024评论 3 440
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,170评论 0 287
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,709评论 1 333
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,597评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,784评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,291评论 5 357
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,029评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,407评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,663评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,403评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,746评论 2 370

推荐阅读更多精彩内容