前言
在理解闭包之前,我们应该先理解JavaScript闭包存在的理由和原因
JavaScript的垃圾回收机制
1.垃圾回收机制
和C#、Java一样JavaScript有自动垃圾回收机制,也就是说执行环境会负责管理代码执行过程中使用的内存,在开发过程中就无需考虑内存分配及无用内存的回收问题了。JavaScript 垃圾回收 的机制很简单:找出 不再使用的变量,然后 释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
2.变量生命周期
不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束。一旦函数结束,局部变量就没有存在必要,可以释放它们占用的内存。但往往垃圾回收器还必须判断哪个变量有用,哪个变量没用,对于不再有用的变量打上标记,以备将来回收。常见的有两种方式:
1.了解.标记清除(mark and sweep)
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如 函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。
至于怎么标记有很多种方式,比如特殊位的反转、维护一个列表等,这些并不重要,重要的是使用什么策略,原则上讲不能够释放进入环境的变量所占的内存,它们随时可能会被调用的到。
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了,然后垃圾回收器相会这些带有标记的变量机器所占空间。大部分浏览器都是使用这种方式进行垃圾回收,区别在于如何标记及垃圾回收间隔而已,只有低版本IE与众不同。
2.了解.引用计数(reference counting)
垃圾IE所使用的回收机制。在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加 1,如果该变量的值变成了另外一个,则这个值得引用次数减 1,当这个值的引用次数变为0的时候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
JavaScript闭包探究
闭包的最大作用在于封装私有的变量,使得外部只能按照写定的方式去操作,而不能够随意控制操作。保存自己私有的变量(也就是局部变量),通过提供的接口(方法)给外部使用,但外部不能直接访问该变量。
1.闭包的理解
所谓的闭包,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层函数体中的临时变量。这使得只要目标对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。
尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。即使 再次调用相同的构造函数,但只会生成 新对象和方法,新的临时变量只是对应新的值,和上次那次调用的是各自独立的!
2.使用
var m = function() {
var x = 1;
var A = function() {
x += 1;
console.log("函数中第一个X的值:" + x);
}
var o = (function() {
console.log("函数中第二个X的值:" + x);
return x;
})();
console.log("函数中第三个X的值:" + o);
return A;
};
var K = m();//"函数中第二个X的值:1" "函数中第三个X的值:1"
K(); //"函数中第一个X的值:2"
var M = m();//"函数中第二个X的值:1" "函数中第三个X的值:1"
M(); //"函数中第一个X的值:2"
M(); //"函数中第一个X的值:3"
K(); //"函数中第一个X的值:3" 注释:该输出证明形成闭包,不同实例形成各自的属性数据
思考…
m是属于被赋值变量,赋值的是一个函数表达式,运行m(),并不会 运行 A() 方法,同样,也只是给 A 赋值一个函数表达式而已,A的内部不会运行。
注意到上述中的另一个被赋予函数体的变量:变量o,与变量m不同的是,方法中的变量o被赋值于一个自执行的匿名函数,所以变量 o的值不是一个方法体,而是return x 的值,即不需要运行o(),将返回变量o的值就将为x的值。另外,"return"所返回的数字或者变量将仅限返回一层。
当执行完"m()"之时,被赋予给m的方法体中的语句都将被执行,按照 JavaScript的垃圾回收机制,诸如方法体中类似x这样的变量在方法结束后,都会被垃圾回收机制收回。
但是由于(闭包的使用原理):m的方法体返回的是内部的一个变量 A,而这个变量 A刚好在方法体中又使用到了变量x,因此,就算在执行【var K = m();】语句之后,系统将会判定:后续只要执行【K()】语句都将可能会使用到变量x,因此x变量是不需要被清理的临时变量,这样便导致了只要【K()】的存在,系统就不会回收变量x的值,并且在继续使用【K();】可以继续操作x变量。
值得注意的是:若重新运行m()【var M = m();–重新运行了"m()"】,注意,此时将会生成新对象以及新属性和新方法,并且该对象所有的对象和方法与之前对象是各自相互独立的。
场景示例
实现多个独立倒计时(同样的倒计时间隔),可动态添加倒计时成员。
var niTime = function(){
/*内部变量数组*/
var innerArray = [];
/*倒计时,循环执行内部函数*/
setInterval(innerFun,1000);
/*该方法为函数入口。外部利用该入口填充数据,至该函数体内变量:数组 ---【闭包用法】填充数据单位为对象,每次填充一个对象单位,该单位包括两个属
性:触发倒计时的位置、倒计时的总时间*/
return function(addressid,T){
innerArray.push({address:document.getElementById(addressid),time:T});
}
/*内部函数,循环数组所有的对象,将每个对象的两个属性,一一对应填充*/
function innerFun(){
for(var i =0;i<innerArray.length;i++){
//以数组为容器,循环数组
innerArray[i].address.innerHTML = innerArray[i].time--;
}
}
}();