由浅入深理解闭包的运行机制

来源:互联网 发布:软件软件下载 编辑:程序博客网 时间:2024/06/04 00:58

先来看看闭包的定义:指有权访问另一个函数作用域中的变量的函数。创建闭包的常用方式就是在一个函数内部创建另一个函数,先来看一个例子:

function createComparsionFunction(propertyName) {    return function(object1, object2) {        var value1 = object1[propertyName];        var value2 = object2[propertyName];        if(value1 < value2) {            return -1;    } else if(value1 > value2) {        return 1;    } else {        return 0;    }    };}var compare = createComparsionFunction('name');var result = compare({name: 'Jack'}, {name: 'Li'});

理解闭包前,我们必须先得理解作用域链,看如下图:
这里写图片描述

当createComparsionFunction被调用时,会创建一个执行环境及相应的作用域链还有创建包含arguments和参数propertyName的活动对象,返回的匿名函数也会创建一个执行环境和相应的作用域链以及创建包含arguments和参数object1、object2的活动对象。从图中可以看出作用域链的本质实际上是一个指向活动对象的指针列表,它只是引用但不实际包含活动对象。

无论什么时候在函数中访问一个变量时,都会从作用域链中搜索相应名字的变量,当函数执行完毕后,局部活动对象中的变量会被销毁,内存中仅保存全局作用域,但是,闭包却不是这样。我们看看上面的例子和图,由于要返回的匿名函数是在createComparsionFunction内部创建的,于是该匿名函数会把外部函数(即createComparsionFunction)的活动对象添加到它的作用域链中,这样匿名函数就可以访问外部函数(即createComparsionFunction)的所有变量,即使createComparsionFunction函数执行完毕后,它的活动对象也不会销毁,因为匿名函数的作用域链仍然在引用它的活动对象,它的活动对象仍然保存在内存中,直到匿名函数被销毁后,它的活动对象才会被销毁。
这就是闭包的本质:内部函数能够访问外部函数的所有变量。


作用域链会引出一个值得注意的副作用:即闭包只能取得包含函数中任何变得最后一个值。看例子:

function createFunction() {    var result = [];    for(var i = 0; i < 10; i++) {        result[i] = function() {        return i;    };    }    return result;}

我们认为输出的是0123….但是实际输出的是10,10…..都返回10,。因为result[i]保存的每个函数都保存着createFunction的活动对象,它们引用的都是同一个变量i。当createFunction执行完毕返回时,i变为了10,而此时每个函数都引用的是同一个变量i,所以每个函数内部返回的i都是10。
解决办法是:我们可以创建另一个匿名函数强制闭包的行为符合预期

function createFunction() {    var result = [];    for(var i = 0; i < 10; i++) {        result[i] = function(num) {           return function() {               return num;           }         }(i);    }    return result;}

函数参数是按值传递的,循环过程中变量i的当前值复制参数num,在该匿名函数又创建了一个返回num的闭包,由于result[i]中的每个函数中的num变量都是不同的数值,所以达到了预期的结果。


在闭包中使用this对象可能会导致一些问题。我们知道,this对象是在运行期间基于函数的运行环境绑定的:在全局函数中,this等于window,而当函数最为某个对象调用时,this等于该对象,由于匿名函数的执行环境具有全局性,因此其this对象通常指向window,但是有时候由于编写的闭包方式不一样,就表现的不那么明显。例子:

var name = 'window';var object = {    name: 'object',    getNameFunc: function() {         return function() {             return this.name;         };    }    };alert(object.getNameFunc()()); //window

为什么会这样呢?前面提到过每个函数在被调用时会创建arguments和this这两个变量,内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能访问到外部函数中的这两个变量,又由于匿名函数具有全局性,因此输出的是window下的name变量。
不过,我们可以把外部的this对象保存在闭包能够访问到的变量里,这样闭包就可以访问外部函数的对象。例如:

var name = 'window';var object = {    name: 'object',    getNameFunc: function() {         var that = this;         return function() {             return that.name;         };    }    };alert(object.getNameFunc()()); //object

that是闭包里的变量,而且that引用的是object对象,所以就可以访问外部函数的name变量。arguments做法也一样。

过度使用闭包会导致内存资源紧张,这是因为闭包携带者外部函数的作用域。


接下来看看闭包可能会产生内存泄漏问题(IE中):

function handler() {    var element = document.getElementById('some');    element.onclick = function() {        alert(element.id);    };}

由于匿名函数一直引用element变量,只要匿名函数存在,element的引用数至少是1,因此它所占用的内存就永远不会释放,稍微改下代码解决:

function handler() {    var element = document.getElementById('some');    var id = element.id;    element.onclick = function() {        alert(id);    };    element = null;}
0 0
原创粉丝点击