闭包的概念:我的理解是,当一个函数被嵌套在另一个函数中,并且在外层函数执行结束之后,内层函数可以继续访问外层函数中的变量。使用闭包可以实现类似私有变量的功能,并且能够阻止外层函数被GC回收。
闭包的使用方法:
1、作为名字空间的调用对象。如一个js代码可能被多个项目引用,而为了避免名字的冲突,可以使用如下的方式去避免它。
(function ($) {
})(jQuery);
比如在我们的项目中,因为$被全局的js代码改写了它,所以$就不再是jQuery了,但是我们函数内部还是希望能够使用$,所以就把jQuery作为参数传递给内部函数使用并且调用它。在函数内部,我们就可以执行我们想要的操作。
2、嵌入函数。
uniqueId = (function () {
var id = 0;
return function () {
return id++;
}
})();
uniqueId(); //1
uniqueId(); //2
该方法使得当外部的函数被调用之后,内部的函数被返回,所以外部的函数就不会被GC回收,从而变量id也一直存在在内存中,从而实现id的自增加。还有一个使用闭包的例子就是能够使变量成为私用属性,然后使用内部函数去修改和访问它。使其从外表上看起来是一个好的封装。
下面这个例子再次给我们展示了一个私有的变量如何被多个函数共享。
function makePeropery(o, name, predicate) {
var value;
o["get" + name] = function () {
return value;
};
o["set" + name] = function (v) {
if (predicate && !predicate(v)) {
throw "set" + name + ":invalid";
}
else {
value = v;
}
};
}
var o = {};
makePeropery(o, "name", function (x) {
return typeof x == "string"
});
o.setname("Frank");
t = o.getname();//"Frank"
3、闭包的问题
在IE中使用会导致内存泄漏。因为IE中这些客户端元素的回收都是使用引用计数的,如果在一个闭包的函数内的任意一个函数参数或者变量引用了一个客户端元素,那么就有可能导致一次内存泄漏。
闭包会使变量都保存在内存中,不被释放,所以内存消耗很大,不能滥用闭包。
闭包外函数外部会改变函数内部的值,所以使用上要小心不要随便改变父函数内部的变量值。特别是把父元素当作object使用时。
4、闭包的实现原理
1、函数的执行环境 excution context: 当函数执行的时候,就是入行相应的执行环境。在创建执行环境的过程中,首先会为函数添加一个scope属性,就是函数的作用域,这个作用域就是定义该函数时候的作用域。如函数如果为a,那么a.scope=a.
2、活动对象 call object :执行环境添加了scope属性之后,就会创建一个call object.它也是一个拥有属性的对象。在创建完活动对象之后,把活动对象添加到函数作用域链的最顶端。然后在活动对象上添加一个arguments属性,该属性中保存着调用函数是所用传递的参数。最后把函数的形参和内部的函数的引用也添加到外部函数的活动对象上。也就是在这一步中,完成了对内部函数的定义,内部函数的作用域链被设置为内部函数所被定义的环境,就是外部函数的作用域。所以内部函数的作用域链会包含外部函数的call object. 所以当内部函数没有消亡的时候,外部函数也不会被消亡。 并且在内部函数中查找变量会先搜索自身的活动变量,然后到外部的活动变量,然后到全局的活动变量。所有内部函数存在prototype原型对象,那么在查找完自身的活动对象,就查找原型对象,然后继续查找作用域链中的活动对象。
3、作用域 scope: js是通过词法划分的,所以这个语言是在定义它们的作用域里面运行的。当定义了一个函数,当前的作用域就保存起来了,并且成为函数的内部状态的一部分。在函数被定义时虽然作用域链确定了,但是作用域链中的属性并没有确定。 我们有两种类型的作用域,一个是全局作用域,一个是函数作用域。全局的作用域链为widow。函数的作用域即变量查找的顺序,当前的call object -- .... -- window object.
4、作用域链 scope chain: 作用域链包括了活动对象,他在作用域链的最顶端,然后就是定义时候的作用域链。
所以闭包的外部函数能够不被GC回收的原因也就是因为内部函数的作用域链包含了外部函数的活动对象。所以外部函数的活动对象也不会被回收。