注册 登录

清河洛

javascript中的闭包①

qingheluo2016-12-16清河洛292
闭包:闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。通过闭包可以返回局部变量function box(){var user = 'Lee'; return function(){return user;};}//通过匿名函数返回box()局部变量user alert(box()()); //通过box()()来直接调用匿名函数返回值 var b = box();alert(b());//另一种调用匿名函数返回值 使用闭包有一个优点,也是它的缺点:就是可以把局部变量驻留在内存...

闭包:闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。

通过闭包可以返回局部变量

function box(){var user = 'Lee'; return function(){return user;};}//通过匿名函数返回box()局部变量user
alert(box()()); //通过box()()来直接调用匿名函数返回值
var b = box();alert(b());//另一种调用匿名函数返回值

使用闭包有一个优点,也是它的缺点:就是可以把局部变量驻留在内存中,可以避免使用全局变量。(全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,所以推荐使用私有的,封装的局部变量)。

通过局部变量无法实现累加

function box() {var age = 100; age ++; return age;}
alert(box()); //101
alert(box()); //101,第二次调用依然是101,无法实现累加,因为又被初始化了

通过闭包可以实现局部变量的累加

function box() {var age = 100;
    return function () {age ++; return age;}
}

var b=box(); //只有在定义变量b的时候才调用函数box()并初始化,之后调用变量b不会初始化box()。
alert(b()); //101,调用匿名函数
alert(b()); //102,第二次调用匿名函数,实现了累加

这里如果使用box()()方法调用匿名函数的话不会实现累加,因为每次调用box()时age还是会初始化。

由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。

所以在调用完之后加一个b=null;来解除引用,等待资源回收

作用域链的机制导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值:

function box(){var arr = [];
    for (var i = 0; i < 5; i++) {
        arr[i] = function () {return i;};
    }
    return arr;
}

var b = box(); //得到函数数组
for (var i = 0; i < b.length; i++){alert(b[i]());}//输出每个函数的值,都是最后一个值5

上面的例子输出的结果都是循环后得到的最大的i值。因为b[i]调用的是匿名函数,匿名函数并没有自我执行,等到调用的时候,box()已执行完毕,局部变量i早已变成5,所以最终的结果就是5个5。我们可以采用以下方法解决这个问题:

1、我执行匿名函数

function box() {var arr = [];
    for (var i = 0; i < 5; i++) {
        arr[i] = (function (num){return num;})(i); //自我传参并且执行
    }
    return arr;
}

var b = box();
for (var i = 0; i < b.length; i++) {alert(b[i]);}//这里返回的是数组,直接打印即可

上例中我们让匿名函数进行自我执行,导致最终返回给arr[i]的是数组的中的每个的值而不是函数了。最终b[0]-b[4]中保留了0,1,2,3,4的值。

2、匿名函数下再做个匿名函数

function box() {
    var arr = [];
    for (var i = 0; i < 5; i++) {
        arr[i] = (function (num) {
            return function () {return num;}//原理和上面例子一样,但是返回的是一个匿名函数
                                            //function (){return num;},该函数外层的匿名函数已经自我传参并执行
        })(i);
    }
    return arr;
}

var b = box();
for (var i = 0; i < b.length; i++) {alert(b[i]());}//这里通过b[i]()函数调用即可

上面两个示例中,我们通过匿名函数自我执行,每次循环的i值,都是调用方通过按值即时传递的,所以最终返回的都是指定的递增的i。而不是box()函数里的i。

闭包中的this对象

在闭包中使用this对象也可能会导致一些问题,this对象是在运行时基于函数的执行环境绑定的,如果this在全局范围就是window,如果在对象内部就指向这个对象。而闭包却在运行时指向window的,因为闭包并不属于这个对象的属性或方法。

var user = 'The Window';
var obj = {
    user : 'The Object',
    getUserFunction : function () {
        return function () {return this.user;};//闭包不属于obj,里面的this指向window
    }
};

alert(obj.getUserFunction()()); //The window

可以强制指向某个对象:alert(obj.getUserFunction().call(obj));//The Object

也可以从上一个作用域中得到对象

getUserFunction : function () {
    var that = this; //从对象的方法里得对象
    return function () {return that.user;};
}

内存泄漏

由于IE的JScript对象和DOM对象使用不同的垃圾收集方式,因此闭包在IE中会导致一些问题。就是内存泄漏的问题,也就是无法销毁驻留在内存中的元素。

function box() {
    var oDiv = document.getElementById('oDiv'); //oDiv用完之后一直驻留在内存
    oDiv.onclick = function () {alert(oDiv.innerHTML);};//这里用oDiv导致内存泄漏
}

那么在最后应该将oDiv解除引用来避免内存泄漏。

function box() {
    var oDiv = document.getElementById('oDiv');
    var text = oDiv.innerHTML;
    oDiv.onclick = function () {
        alert(text);
    };
    oDiv = null;//解除引用
}

如果并没有使用解除引用,那么需要等到浏览器关闭才得以释放。



网址导航