zoukankan      html  css  js  c++  java
  • JavaScript闭包

    对于JavaScript初学者来说,闭包是一个很神秘的东西,不管看多少遍,依旧搞不清楚闭包是什么,更不明白其内部是什么样的处理机制(更可恶的是每次面试都会被问到)。

      说的含糊一点,闭包就是代码块和该代码块上下文(context)相互作用的产物。看一个例子:

    function foo(){
        var x = 1;
    
        return function (){
            alert(++x); //2
        }
    }
    
    var bar = foo();
    bar();

      先问一个问题,这里到底谁是闭包?是foo还是那个匿名函数?

    闭包的产生原理

      在JavaScript中,函数可以用来分隔作用域,当foo执行(activation)的时候,产生了一个foo的动态作用域,然后这个动态作用域把变量x和那个return的匿名函数装(push到栈)了进去,一般情况下,当函数执行完毕时,它会自动销毁(pop出栈)内部产生的变量和函数,跳出这个作用域环境,返回到上一层(context)。但是在这里,由于foo作用域内部的变量和函数与它作用域外部的变量bar存在暧昧关系(bar引用了foo()的返回值),所以变量x和匿名函数没法从foo作用域中被销毁,于是也就产生了我们平时所说的闭包。刚才说的push到栈和pop出栈很已经显然不适用于闭包,这和栈的结构是相悖的,那么闭包是怎样的内存分配方式呢?这个我们后面再说。闭包既不是foo函数,也不是那个匿名函数,而是变量x、匿名函数、上下文环境三者一起同时存在的结果。

      闭包存在有这么两个条件

    • 没有被创建它的上下文销毁
    • 引用了自由变量(没有在函数块中定义,也没有从arguments中送入,如上匿名函数中的变量x,就是一个自由变量)

      说了这么多,再看看下面这个例子:

    var x = 1;
    function foo(){
        alert(x);
    }
    
    ~function(){
        var x = 2;
    
        foo(); //1
    }();

      你可能又不解了,这里怎么会弹出1呢?先说明下,下面三种写法效果是等价的(但解析方式并不一样,A、C是一类,B是另一类,这里就不多说了):

    ~function(){
        var x = 2;
    
        foo();    
    }(); //A
    
    (function(){
        var x = 2;
    
        foo();    
    }()); //B
    
    (function(){
        var x = 2;
    
        foo();    
    })(); //C

    闭包的内存分配方式

      回归正题,上面为什么会弹出1,这个闭包的情况和上面所述的闭包有些不太相同,上面的闭包是因为作用域中的东西没有被销毁,并与上下文存在暧昧关系,而这里并不存在销毁什么的问题,但是它依旧是一个闭包。在foo中,x是一个自由变量,当foo这个闭包产生的时候,foo的上下文会被保存,而foo处于Activation状态的时候,它会先从他所处的Activation Object(foo内部声明的变量、函数等非自由变量)中查找需要的对象,如果没有找到,便会从它开始保存的上下文中查找对象,如果还没找到,才会跑到他的上一层作用域链中取那个值为2的x。

      再回到之前说的那个问题,闭包的内存分配方式。很明显,如果闭包的内存分配是利用栈的结构实现的,那进入foo运行状态的时候,应该会push一个“全局“的x,也就是向上找到那个var x = 2,接着alert(2);但事实并非如此,上层作用域的闭包数据是动态分配的内存,也就是保存在堆里,解析器会记录这个闭包数据被引用的次数,当引用次数为0的时候,垃圾回收机制(GC)会自动处理这些垃圾。

    闭包是怎么导致内存溢出的

      IE经常会因为闭包的存在而导致内存溢出。第一个例子中:

    window <=> foo <=> 匿名函数 <=> bar <=> window

      形成了一个引用循环,即便是

    delete bar;

      这个匿名函数的引用次数依旧大于0,需要注意的是delete一个变量并不是删除这个变量的引用对象,而是断开这个引用,其作用就是让引用对象的引用次数减1. 这样一来,这个闭包就死在内存里了,于是它也就一直占用着内存= =

    小结

      原型链、闭包、作用域链的学习,除了对这些基本知识有一定了解之外,还需要比较多的尝试和实践才能理解透彻。很多次想说说闭包的含义,但是每次提笔又觉得自己没有想明白,只好作罢。这一次对闭包的浅析,肯定也存在很多不到位或者描述错误的地方,如果有不同的见解,请提出来,大家相互学习!!!

     

    作者:靖鸣君
    出处:http://www.cnblogs.com/hustskyking/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
  • 相关阅读:
    Windows 右键添加用记事本打开的选项
    shell 脚本常用写法
    常用命令--dig
    电子表格数字式的小时化成时分秒格式
    Kaggle猫狗图像分类竞赛Baseline
    阿里巴巴用户体验研究专员暑期实习生笔试 经验分享 2019
    sysctl -w net.core.somaxconn=65535
    src/stream/ngx_stream_proxy_module.c:542: 错误:‘ngx_stream_upstream_t’没有名为‘ssl_name’的成员
    ssl.cpp:333: error: ‘SSL_set_tlsext_host_name’ was not declared in this scope
    fiddler QuickExec
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3335844.html
Copyright © 2011-2022 走看看