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

    function a(){
        var n = 0;
        this.inc = function () {
            n++; 
            console.log(n);
        };
    }
    var c = new a();
    c.inc();    //控制台输出1
    c.inc();    //控制台输出2

    什么是闭包?这就是闭包!!有权访问另一个函数作用域内变量的函数都是闭包。当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

    这里 inc 函数访问了构造函数 a 里面的变量 n,所以形成了一个闭包。

    function a(){
        var n = 0;
        function inc(){
           n++; 
           console.log(n);
        }
        return inc;
    }
    var c = a();
    c();    //控制台输出1
    c();    //控制台输出2

    执行过程:

    var c=a();这里的a()返回的是函数inc,那这句等同于var c = inc;c()相当于inc();函数名只是一个标识(指向函数的指针),而()才是执行函数。

    为什么要用闭包?

    js的每个函数都是一个个小黑屋,它可以获取外界信息,但是外界却无法直接看到里面的内容。将变量 n 放进小黑屋里,除了 inc 函数之外,没有其他办法能接触到变量 n,而且在函数 a 外定义同名的变量 n 也是互不影响的,这就是所谓的增强“封装性”。而之所以要用 return 返回函数标识 inc,是因为在 a 函数外部无法直接调用 inc 函数,所以 return inc 与外部联系起来,第一个代码中的 this 也是将 inc 与外部联系起来而已。

    常见的陷阱

    function createFunctions(){
        var result = new Array();
        for (var i=0; i < 10; i++){
            result[i] = function(){
                return i;
            };
        }
        return result;
    }
    var funcs = createFunctions();
    for (var i=0; i < funcs.length; i++){
        console.log(funcs[i]());
    }

    原本想要输出0~9,但是最后的结果是10个10,为什么呢?

    这里的陷阱就是:函数带()才是执行函数! 单纯的一句 var f = function() { alert('Hi'); }; 是不会弹窗的,后面接一句 f(); 才会执行函数内部的代码。上面代码翻译一下就是:

    var result = new Array(), i;
    result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
    result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
    ...
    result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
    i = 10;
    funcs = result;
    result = null;
    
    console.log(i); // funcs[0]()就是执行 return i 语句,就是返回10
    console.log(i); // funcs[1]()就是执行 return i 语句,就是返回10
    ...
    console.log(i); // funcs[9]()就是执行 return i 语句,就是返回10

    为什么只垃圾回收了 result,但却不收了 i 呢? 因为 i 还在被 function 引用着啊。这就是闭包的神奇之处,内部作用域依然存在,因此没有被回收。result依然持有对该作用域的引用,这个引用就叫做闭包!

    更改方法:

    function createFunctions(){
        var result = new Array();
        for (var i=0; i < 10; i++){
            result[i] = function(x){
                return function(){
                    return x;
                }
            }(i);
        }
        return result;
    }
    var funcs = createFunctions();
    for (var i=0; i < funcs.length; i++){
        console.log(funcs[i]());
    }

    没有直接把闭包赋值给数组,而是定义了一个匿名函数,并通过立即执行该匿名函数的结果赋值给数组,并带了for循环的参数i进去,让x能找到传入的参数值为0-10,这就解释了函数参数是按值传递的,所以会将变量i的当前值复制给参数x。而这个匿名函数内部又创建并返回了一个访问x的闭包。这样以来result数组中的每个函数都有自己x变量的一个副本,所以会符合我们的预期输出不同的值。

    闭包的应用场景

    闭包应用的两种情况:函数作为返回值,函数作为参数传递

    1、函数作为返回值

    function fn(){
        var max=10;
        return function bar(x){
            if (x>max){
                 console.log(x);
            }
        };  
    }
    var f1=fn();
    f1(15);

    bar函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值。

    2、函数作为参数被传递

    var max = 10;
    var fn=function(x){
       if(x>max){
          console.log(x);
       }
    };
    (function(f){
       var max = 100;
       f(15);
    })(fn);

    fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100,因为要去创建这个函数的作用域取值,而不是“父作用域”。

    当一个函数被调用完成之后,其执行上下文环境将被销毁,其中的变量也会被同时销毁。但有些情况下,函数调用完成之后,其执行上下文环境不会接着被销毁。这就是需要理解闭包的核心内容。如下:

    第一步,代码执行前生成全局上下文环境,并在执行时对其中的变量进行赋值。此时全局上下文环境是活动状态。

     

    第二步,执行第17行代码时,调用fn(),产生fn()执行上下文环境,压栈,并设置为活动状态。

    第三步,执行完第17行,fn()调用完成。按理说应该销毁掉fn()的执行上下文环境,但是这里不能这么做。注意,重点来了:因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的作用域。而正巧合的是,返回的这个函数体中,还有一个自由变量max要引用fn作用域下的fn()上下文环境中的max。因此,这个max不能被销毁,销毁了之后bar函数中的max就找不到值了。

    因此,这里的fn()上下文环境不能被销毁,还依然存在与执行上下文栈中。——即,执行到第18行时,全局上下文环境将变为活动状态,但是fn()上下文环境依然会在执行上下文栈中。另外,执行完第18行,全局上下文环境中的max被赋值为100。如下图:

    第四步,执行到第20行,执行f1(15),即执行bar(15),创建bar(15)上下文环境,并将其设置为活动状态。

    执行bar(15)时,max是自由变量,需要向创建bar函数的作用域中查找,找到了max的值为10。这个过程在作用域链一节已经讲过。

    这里的重点就在于,创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了。

    这里明显可以看到,使用闭包会增加内容开销!

    第五步,执行完20行就是上下文环境的销毁过程。

    总结

    闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!另外使用闭包也要注意变量的值是否符合你的要求,因为他就像一个静态私有变量一样。

  • 相关阅读:
    Java学习笔记12---向上转型-父类的对象引用指向子类对象
    Java学习笔记11---静态成员变量、静态代码块、成员变量及构造方法的初始化或调用顺序
    Java学习笔记10---访问权限修饰符如何控制成员变量、成员方法及类的访问范围
    Java学习笔记9---类静态成员变量的存储位置及JVM的内存划分
    Java学习笔记8---类的静态成员变量与静态成员方法的访问与调用方式
    Java学习笔记7---父类构造方法有无参数对子类的影响
    Java学习笔记6---字符串比较方法compareTo(String str)
    地址总线、数据总线、寻址能力、字长及cpu位数等概念之间的关系
    Alpha事后诸葛亮
    第05组 Alpha冲刺(4/4)
  • 原文地址:https://www.cnblogs.com/lmjZone/p/9400300.html
Copyright © 2011-2022 走看看