闭包

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中可能导致内存泄漏。解决办法是,在退出函数之前,将不是用的局部变量全部删除。

 

posted @ 2019-09-17 20:28  焱雨  阅读(100)  评论(0编辑  收藏  举报