• 闭包


    1.闭包(Closure):指有权访问另一个函数作用域中的变量的函数。

    function f1() {
        var n = 999;
        function f2() {
            console.log(n);
        }
        return f2;
    }
    var result = f1();
    result(); //999

    上面的代码中的f2函数就是闭包。

     1 function createComparisonFunction(propertyName) {
     2     return function(object1, object2) {
     3         var value1 = object1[propertyName);
     4         var value2 = object2[propertyName);
     5         if(value1 < value2) {
     6             return -1;
     7         } else if (value1 > value2) {
     8             return 1;
     9         } else {
    10             return 0;
    11         }
    12     };
    13 }

    作用域链对理解闭包至关重要。当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境。

    在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。

    1 var compare = createComparisonFunction("name");
    2 var result = compare({name: 'Nicholas'}, {name: 'Greg'});

    下图展示了当上述代码执行时,包含函数与内部匿名函数的作用域链。

    在匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunciton()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunciton()中定义的所有变量。更为重要的是,createComparisonFunciton()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当createComparisonFunciton()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直至匿名函数被销毁后,createComparisonFunciton()的活动对象才会被销毁

    1 //创建函数
    2 var compareNames = createComparisonFunction('name');
    3 
    4 //调用函数
    5 var result = compareNames({name: "Nicholas"}, {name: "Greg"});
    6 
    7 //解除对匿名函数的引用(以便释放内存)
    8 compareNames = null;

    通过将compareNames设置为等于null解除对该函数的引用,就等于通知垃圾回收例程将其清除。随着匿名函数的作用域链被销毁,其他作用域(除了全局作用域)也都可以安全地销毁了。

    2.闭包与变量

    作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。

    1 function createFunctions() {
    2     var result = new Array();
    3     for(var i = 0; i < 10; i++) {
    4         result[i] = function() {
    5             return i;
    6         };
    7     }
    8     return result;   //[10,10,10,10,10,10,10,10,10,10]
    9 }

    这个函数会返回一个函数数组,每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10。

     1 function createFunctions() {
     2     var result = new Array();
     3     for(var i=0; i<10; i++) {
     4         result[i] = function(num) {
     5             return function() {
     6                 return num;
     7             };
     8         }(i);  //立即执行函数
     9     }
    10     return result;
    11 }

    上述函数执行的结果符合我们的预期。

    3.关于this对象

    在闭包中使用this对象可能会导致一些问题。this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过匿名函数的执行环境具有全局性,因此其this对象通常指向window(如果通过call()或apply()改变函数执行环境的情况下,this就会指向其他对象)。

     1 var name = "The Window";
     2 
     3 var object = {
     4     name: "My Object",
     5     getNameFunc: function() {
     6         return function () {
     7             return this.name;
     8         };
     9     }
    10 };
    11 
    12 alert(object.getNameFunc()());   //"The Window"

    为什么匿名函数没有取得其包含作用域(或外部作用域)的this对象呢? 因为,每个函数被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。

     1 var name = "The Window";
     2 var object = {
     3     name: "My Object",
     4     getNameFunc: function() {
     5         var that = this;
     6         return function() {
     7             return that.name;
     8         };
     9     }
    10 };
    11 
    12 alert(object.getNameFunc()());  //"My Object"

    在定义匿名函数之前,把this对象赋值给一个名叫that的变量。而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声明的一个变量。

    注:this和arguments存在同样的问题。如果想访问作用域中的arguments对象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。

    4.闭包之间的交互

     1 function outerFn() {
     2     var outerVar = 0;
     3     function innerFn1() {
     4         outerVar++;
     5         console.log('(1) outerVar = ' + outerVar);
     6     }
     7     function innerFn2() {
     8         outerVar += 2;
     9         console.log('(2) outerVar = ' + outerVar);
    10     }
    11     return {'fn1': innerFn1, 'fn2': innerFn2};
    12 }
    13 
    14 var fnRef = outerFn();
    15 fnRef.fn1();
    16 fnRef.fn2();
    17 fnRef.fn1();
    18 
    19 var fnRef2 = outerFn();
    20 fnRef2.fn1();
    21 fnRef2.fn2();
    22 fnRef2.fn1();
    23 
    24 //(1) outerVar = 1
    25 //(2) outerVar = 3
    26 //(1) outerVar = 4
    27 //(1) outerVar = 1
    28 //(2) outerVar = 3
    29 //(1) outerVar = 4

    这两个内部函数(闭包)引用了同一个局部变量,因此它们共享了一个封闭环境。outerFn()函数实际返回的是一个对象,局部变量outerVar就是这个对象的实例变量,而闭包就是这个对象的实例方法。而且,这些变量也是私有的,因为不能在封装它们的作用域外部直接引用这些变量,从而确保了数据的专有特性。

    5.闭包的用途

    闭包可以用在许多地方。它的最大用处有两个,一个可以读取函数内部的变量。另一个就是让这些变量的值始终保持在内存中。

     1 function f1() {
     2     var n = 999;
     3     nAdd = function() { n += 1}  //nAdd全局变量, 也是一个闭包,可以在函数外部对函数内部的局部变量进行操作
     4     function f2() {
     5         alert(n);
     6     }
     7     return f2;
     8 }
     9 
    10 var result = f1();
    11 result();  //999
    12 nAdd();
    13 result(); //1000

    在上面代码中,result实际上就是一个闭包。它一共运行了2次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量 n 一直保存在内存中,并没有在f1 调用后被自动清除。

    原因在于f1 是f2 的父函数,而f2 被赋给了一个全局变量,导致f2 始终在内存中,而f2 的存在依赖于f1,因此f1 也始终在内存中,不会调用结束后,被垃圾回收机制回收。

    6.使用闭包导致的问题

    由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏。解决办法是,在退出函数之前,将不是用的局部变量全部删除。

  • 相关阅读:
    Visual Studio2019安装步骤
    写在第一页的话
    数状数组
    hdu 3501 数学题
    静态邻接表
    最长子序列
    hdu 1094 所想到的
    bellman_ford
    郁闷的一晚
    SPFA + 静态邻接表 模板
  • 原文地址:https://www.cnblogs.com/sparkler/p/13686964.html
Copyright © 2020-2023  润新知