You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循环引用
el.onclick = function(){
alert(id);
}
el = null; // 将闭包引用的外部函数中活动对象清除
}
var name ="The Window" ;
var object = {
name : "My Object" ,
getNameFunc : function(){
**var that = this ;**
return function(){
**return that.name**
}
}
}
alert(object.getNameFunc( )( )) ; //"My Object"
什么是闭包
来看一些关于闭包的定义:
来个定义总结
创建一个简单的闭包
解读:
创建一个匿名函数并将它赋值给变量sayName,匿名函数中返回的函数引用了外部函数的变量name,从而形成了闭包,由于垃圾回收机制,sayName函数执行完毕后,变量name由于被引用并没有被销毁,继续存在内存当中。
当执行say()的时候 ,执行返回的内部函数,依然能访问变量name,输出 'Ray' .
闭包中的作用域链
讲到闭包就不能不提作用域链,理解作用域链对理解闭包很重要。
先来看个普通的作用域链:
这段代码包含两个作用域:a.全局作用域;b.sayName函数的作用域,也就是只有两个变量对象,当执行到对应的执行环境时,该变量对象会成为活动对象,并被推入到执行环境作用域链的前端,也就是成为优先级最高的那个。 我们来看下面的这张图:
这图在JS高级程序设计书上也有(P179)。
在创建sayName()函数时,会创建一个预先包含变量对象的作用域链,也就是图中索引为1的作用域链,并且被保存到内部的[[Scope]]属性中,当调用sayName()函数的时候,会创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起作用域链,此后,又有一个活动对象(图中索引为0)被创建,并被推入执行环境作用域链的前端。
一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。但是,闭包的情况又有所不同 ;
再来看看看闭包的作用域链:
这个闭包实例比上一个例子多了一个匿名函数的作用域:
这图在JS高级程序设计书上也有(P180)。
在匿名函数从sayName()函数中被返回后,它的作用域链被初始化为包含sayName()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在sayName()中定义的所有变量和参数,更为重要的是,sayName()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链依然在引用这个活动对象,换句话说,sayName()函数执行完后,其执行环境的作用域链会被销毁,但他的活动对象会留在内存中,直到匿名函数销毁。这个也是后面要讲到的内存泄露的问题。
原型链:
在JavaScript中,一共有两种类型的值,原始值和对象值.每个对象都有一个内部属性[[prototype]],我们通常称之为原型.原型的值可以是一个对象,也可以是null.如果它的值是一个对象,则这个对象也一定有自己的原型.这样就形成了一条线性的链,我们称之为原型链;
作用域链:
一般来说,作用域链是针对变量的,js里面大的范围上来说,只有两种作用域,全局作用域和函数内部作用域,如果函数1里面又定义了函数2(一般都是匿名函数), 那么就有了这么一个作用域链全局作用域==>函数1作用域==>函数2作用域;特点是函数1里面可以直接使用全局作用域的变量,函数2里面可以直接使用全局作用域和函数1作用域的变量
闭包的运用
我们来看看闭包的用途。事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。
1. 匿名自执行函数
实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,
比如UI的初始化,那么我们可以使用闭包
我们在for循环外部插入了一个私有的作用域,在匿名函数里定义的任何变量,都会在执行结束时被销毁。因此,变量 i 只能在循环中使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。
通过这种创建私有作用域的方法,可以防止我们在全局作用域添加过多的变量和函数,导致命名冲突的问题,污染全局对象。同时也减少了闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕就可以立即销毁其作用域链
2. 实现封装
3. 实现面向对象中的对象
这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,
我们可以模拟出这样的机制。还是以上边的例子来讲:
Person的两个实例person1 和 person2 互不干扰!因为这两个实例对name这个成员的访问是独立的 。
闭包造成的内存泄露及解决方案
说到内存管理,自然离不开JS中的垃圾回收机制,有两种策略来实现垃圾回收(GC):标记清除 和 引用计数。
但是严格意义上讲,闭包不是真正产生内存泄漏的原因!关于内存泄漏的详细内容
标记清除:垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后,它会去掉环境中的变量的标记和被环境中的变量引用的变量的标记,此后,如果变量再被标记则表示此变量准备被删除。 2008年为止,IE,Firefox,opera,chrome,Safari的javascript都用使用了该方式;
引用计数:跟踪记录每个值被引用的次数,当声明一个变量并将一个引用类型的值赋给该变量时,这个值的引用次数就是1,如果这个值再被赋值给另一个变量,则引用次数加1。相反,如果一个变量脱离了该值的引用,则该值引用次数减1,当次数为0时,就会等待垃圾收集器的回收。
这个方式存在一个比较大的问题就是循环引用,就是说A对象包含一个指向B的指针,对象B也包含一个指向A的引用。 这就可能造成大量内存得不到回收(内存泄露),因为它们的引用次数永远不可能是 0 。早期的IE版本里(ie4-ie6)采用是计数的垃圾回收机制,闭包导致内存泄露的一个原因就是这个算法的一个缺陷。
我们知道,IE中有一部分对象并不是原生额javascript对象,例如,BOM和DOM中的对象就是以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数。因此,虽然IE的javascript引擎采用的是标记清除策略,但是访问COM对象依然是基于引用计数的,因此只要在IE中设计COM对象就会存在循环引用的问题!(主要是低版本IE的垃圾回收机制问题)
举个例子:
这段代码为什么会造成内存泄漏
执行这段代码的时候,将匿名函数对象赋值给el的onclick属性;然后匿名函数内部又引用了el对象,存在循环引用,所以不能被回收;
解决方法:
总结闭包的优缺点
优点:
缺点
最后的最后。。。我们再来理解两个闭包的经典栗子:
解读:
解读:
The text was updated successfully, but these errors were encountered: