• Javascript闭包


    基础知识

    变量作用域:

    //javascript的变量作用域可以分为两种:全局变量和局部变量。
    //在函数内声明的变量就是局部变量,这个变量在函数体内可访问,在函数外部无法直接读取局部变量。
    //例如:
    var globalVariable = 1; //全局变量
    function f() {
        var localVariable = 100; //局部变量 注意函数内的变量一定要加上关键字var才能成为局部变量,不然就会成为全局变量
    }
    alert(globalVariable); //显示1
    alert(localVariable);  //抛出错误 提示localVariable未声明
    变量作用域
    //javascript内部函数又称为嵌套函数,就是在函数中声明的函数
    //函数可以实现多级嵌套
    //例如:
    function f() {
        function innerFunction() {
            //innerFunction是f的内部函数
            function anotherInnerFunction() {
                //anotherInnerFunction是innerFunction的内部函数
            }
        }
    }
    内部函数
    //局部变量对于内部函数而言是可访问的,整个作用域链只能向下传递。
    //例如:
    function f() {
        var localVariable = 100; //局部变量
        function innerFunction() {
            var anotherLocalVariable = 200; //局部变量
            alert(localVariable);//内部函数可以访问外部函数的局部变量
        }
        alert(anotherLocalVariable); //抛出错误 提示anotherLocalVariable未声明
    }
    变量作用域
    //javascript借鉴了函数式编程的概念,把函数提到了一等公民的地位,意思是函数可以跟变量一样被赋值,作为传入参数和返回参数。
    //例如:
    function f1() {
    }
    var temp1 = f1; //函数可以赋值给变量
    
    function f2(f) {
        f();
    }
    f2(f1); //函数f1可以作为函数f2的参数
    
    function f3() {
        function f4() {
        }
        return f4; //函数f4可以作为函数f3执行后返回的参数
    }
    var temp2 = f3(); //temp2的值是f4
    函数第一公民

    闭包的概念

      如果单纯从字面上来理解"闭包"这两个字,往往让人摸不着头脑,所以我们可以更多地把"闭包"当作一个代号名称,不要从字面上理解这个概念。参考了多处资料,发现闭包的官方定义很晦涩难懂,经过我的理解,觉得闭包的概念可以这样描述:当一个内部函数被调用,就会形成闭包。看代码应该会更清晰一些:

    //在声明它的函数中调用
    function f() {
        var localVariable = 100; //局部变量
        function innerFunction() {
            //访问局部变量
            localVariable += 1;
            alert(localVariable);
        }
        innerFunction();
    }
    f(); //显示101
    //这样子确实也形成了闭包,但暂时没发现这样做有什么意义
    
    
    //在声明它的函数外部调用
    function f() {
        var localVariable = 100; //局部变量
        function innerFunction() {
            //访问局部变量
            localVariable += 1;
            alert(localVariable);
        }
        return innerFunction; //innerFunction作为函数f的返回参数
    }
    var temp = f(); 
    temp(); //显示101
    temp(); //显示102
    //可见,闭包使得函数外部操作局部变量变为可能
    //假如我们只是单纯执行f(),不把结果赋值给全局变量temp,那么执行完后,由于不存在其他引用,这时候函数f就会成为垃圾回收的对象
    //而由于赋值给全局变量temp了,innerFunction引用到f函数,函数f的局部变量localVariable就不会被回收,所以就出现了上文中执行两次temp(),实现累加效果
    内部函数可以使用两种调用方式

    闭包的应用场景

      单纯地知道闭包的概念是远远不够的,要充分理解闭包这个概念,还得从它的使用场景入手。

      其实闭包的应用可以简单总结为一句话:将变量维持在内存,带来更好的安全性和封装性。就像在上一节中使用的demo一样,将localVariable维持在内存中,使得改变它变为可能。其实,要实现这个功能,你可能觉得使用全局变量就可以了,除非重新加载或关闭页面,否则全局变量一直在内存中。但是使用全局变量其实不是一种不好的做法,最主要是因为全局变量是可以手动更改的,例如定义一个globalVariable,你在接下来的代码可以随心所欲地修改,而使用闭包,想要修改变量,就只能通过调用已经实现的特定函数,学过面向对象的人都知道,这就是良好封装性的体现,不能随意修改,就保证了变量的安全性。例如下面的demo:

    function f() {
        var localVariable = 100; //局部变量
        function innerPlus() {
            localVariable += 1;
            alert(localVariable);
        }
        function innerMinus() {
            localVariable -= 1;
            alert(localVariable);
        }
        return {
            plus: innerPlus,
            minus:innerMinus
        }; //这次返回的不再是一个单独的函数,而是一个对象,对象的属性是两个内部函数
    }
    var temp = f();
    temp.plus();  //显示101
    temp.minus(); //显示100
    //只能通过temp的特定API操作变量,实现了封装性和更好的安全性
    Demo

    这里再补充一个Demo,关于内部函数在声明它的函数中调用的实际应用场景。

    //对node节点的背景颜色做一次渐变
    function fadeBgColor(node) {
        var level = 1;
        function fade() {
            if (level < 15) {
                var hex = level.toString(16);//将整型转化为16位的字符 如10对应a
                node.style.backgroundColor = '#FFFF' + hex + hex;
                level += 1;//访问局部变量
                setTimeout(fade, 200);//递归调用自身
            }
        }
        setTimeout(fade, 200);//200ms后执行fade函数
    }
    fadeBgColor(document.body);
    内部函数在声明函数中调用Demo

    深入理解闭包

      接下来的这一小节将从闭包的微观世界说起,展示一段javascipt代码,通过对javascript解析器执行原理的剖析,逐步地探讨闭包的内部机理。

    function f() {
        var localVariable = 100;
        function innerFunction() {
            localVariable += 1;
            alert(localVariable);
        }
        return innerFunction;
    }
    var temp = f();
    temp();
    Demo

      1. javascript解析器会在页面加载后创建一个全局执行上下文(Execution Context),全局执行上下文的作用域链中只有一个全局对象,它定义了javascript中所有可用的全局变量和函数,全局对象在初始化的时候会包含this,window,document等对象,全局对象会随着javascript的执行动态地新增对象。

      2. 当javascript解析器执行到函数声明function f(){...}的时候,因为函数f是一个全局函数,所以会把全局执行上下文的作用域链赋给函数f的内部属性scope(内部属性无法通过javascript直接访问)。

      3. 接下来var temp = f(),这时javascript解析器会执行函数f,同时创建一个对应的执行上下文,并创建一个活动对象,并初始化给this和函数传入参数arguments,同时加入函数f中所有的变量(初始化为undifined),随着函数的执行变量将逐步被赋值。活动对象会出现在执行上下文作用域链的顶端,紧接着是f函数的scope属性中的对象。

      4. 执行f函数的时候,javascript解析器会完成函数innerFunction的声明,这时innerFunction的执行上下文是函数f的执行上下文,所以会把函数f的执行上下文作用域链赋值给innerFunction的scope属性。

      5. 最后执行temp(),其实就是执行innerFunction(),这时候会创建一个对应的新的执行上下文,同时创建temp函数的活动对象。同样活动对象会出现在作用域链的顶端,接下来是scope属性的作用域链。

      此时,函数f从声明到执行的步骤就完成了。最终函数temp的作用域链引用着函数f的活动对象,所以在函数f返回后就不会被GC回收。从这里也可以清晰地看到,函数temp在查找localVariable变量的时候,首先先从自身的活动对象中查找,找不到再到原型对象(有关原型对象这里就暂时不讨论了)中找,之后就到函数f的活动对象中找,再找不到就到全局对象中查找。

    文章来源:teroy的博客

  • 相关阅读:
    被劣质代码“残害”的这些年
    17 个案例带你 5 分钟搞定 Linux 正则表达式
    nginx配置详解
    探究 Go 语言 defer 语句的三种机制
    git 生成ssh
    关于Laravel 与 Nginx 限流策略防止恶意请求
    保持开源项目健康运行并减少压力的 10 件事
    带着canvas去流浪系列之三 绘制饼图
    无码系列-6 数据缓存设计经验谈
    IoT开发精英实战营招募啦!速来报名!
  • 原文地址:https://www.cnblogs.com/haojiahong/p/4865781.html
走看看 - 开发者的网上家园