友情提示,如果你对作用域、作用域链、函数表达式这些知识掌握的不是很好的话,建议您可以先看看深入理解js中的作用域以及作用域
1、什么是闭包?
关于什么是闭包,官方的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。相信很少有人能直接看懂这句话,因为他描述的太学术。而网上对闭包的理解也是众说纷纭,而我的理解就是
闭包就是定义在函数内部并且能够读取其他函数局部变量的函数,或者更通俗的理解:闭包就是函数嵌套函数,内部函数可以引用外部函数的参数和变量,参数和变量不会被垃圾回收机制收回
2、闭包有什么好处?
1>、外围函数可以读取函数内部函数的变量:由于作用域链的结构,外围函数是无法访问内部变量的,为了能够访问内部变量,我们就可以使用闭包。
2>、可以让一个变量长期驻扎在内存中
3>、可以避免全局变量的污染
4>、私有成员的存在
说了这么多,下面我们用例子来看看到底什么是闭包,以及使用闭包的好处
var a=1;
function aaa(){
a++;
alert(a);
}
aaa();//2
aaa();//3
我们想要让a重复累加,这是我们不使用闭包时候的做法。我们可以从代码中看到,我们使用了全局变量。但我们在实际的项目中为了提高性能,是要尽量避免使用全局变量。所以如果我们不使用全局变量结果会是怎样的呢
function aaa(){
var a=1;
a++;
alert(a);
}
aaa();//2
aaa();//2
很显然这并不是我们想要的结果,每次执行a都是2,这是因为局部变量每次执行之后,都会被垃圾回收机制收回,再执行再重新赋值。
那如果我们想要既能重复累加,又使用局部变量,要怎么做呢?对,就是使用闭包,看例子
function aaa(){
var a= 1;
return function(){
a++;
alert(a);
}
}
var b =aaa();
b();//2
b();//3
这就是闭包,函数嵌套函数,内部函数可以引用外部函数的参数和变量
再来看一个例子:
var aaa = (function aaa(){
var a= 1;
return function(){
a++;
alert(a);
}
})();
aaa();//2
aaa();//3
用法:模块化代码,在循环中直接找到对应元素的索引
模块化代码,减少了全局变量的污染
var aaa = (function aaa(){
var a= 1;
function bbb(){
a++;
alert(a);
}
function ccc(){
a++;
alert(a);
}
return {
b:bbb,
c:ccc
}
})();
aaa.b();//2
aaa.c();//3
alert(aaa);//报错
alert(bbb);//报错
alert(ccc);//报错
这和上面的类似,只需要在我们调用的时候注意一下,不要出现上面三种报错的做法。
再看一个我们学习js中常用的一种写法怎样利用闭包来修改
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + i);
}, 'false');
}
// 这个代码是错误的,因为变量i从来就没被locked住
// 相反,当循环执行以后,我们在点击的时候i才获得数值
// 因为这个时候i操真正获得值
// 所以说无论点击哪个连接,最终显示的都是I am link #10(如果有10个a元素的话)
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
(function (lockedInIndex) {
elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + lockedInIndex);
}, 'false');
})(i);
}
/ 这个是可以用的,因为他在自执行函数表达式闭包内部
// i的值作为locked的索引存在,在循环执行结束以后,尽管最后i的值变成了a元素总数(例如10)
// 但闭包内部的lockedInIndex值是没有改变,因为他已经执行完毕了
// 所以当点击连接的时候,结果是正确的
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
elems[i].addEventListener('click', (function (lockedInIndex) {
return function (e) {
e.preventDefault();
alert('I am link #' + lockedInIndex);
};
})(i), 'false');
// 你也可以像下面这样应用,在处理函数那里使用自执行函数表达式
// 而不是在addEventListener外部
// 但是相对来说,上面的代码更具可读性
然后再来看一看阮一峰博客上面的两个关于闭包的思考题,看看你能不能理解了。
var name ="The Window";
var object = {
name : "My Object",getNameFunc : function(){
return function(){
return this.name;
};}
};
alert(object.getNameFunc()());//The Window
var name = "The Window";
var object = {
name : "My Object",getNameFunc : function(){
var that = this;
return function(){
return that.name;
};}
};
alert(object.getNameFunc()());//My Object
其实想要理解这两段代码也并不难,只要我们理解了闭包中的this指向的问题,下面我们就来谈谈,闭包中的this指向问题(这几天,我会整理一下关于this指向的问题,有兴趣的可以关注一下哦)
先看一段代码
var myNumber = {
value: 1,
add: function(i){
var helper = function(i){
console.log(this);
this.value += i;
}
helper(i);
}
}
myNumber.add(1);
1.this指向window对象(因为匿名函数的执行具有全局性,所以其this对象指向window);2.不能实现value加1(每个函数在被调用时都会自动取得两个特殊变量,this和arguments,内部函数在搜索这两个对象时,只会搜索到其活动对象为止,所以不能实现访问外部函数的this对象);
3.修改代码实现正确功能
第一种方法:
var myNumber={
num:1,
add:function(i){
var that=this;//定义变量that用于保存上层函数的this对象(myNumber)
var helper=function(i){
console.log(that);
that.num+=i;
}
helper(i);
}
}
myNumber.add(1);//此时num的值为2
第二种方法
var myNumber={
value:1,
add:function(i){
var helper=function(i){
this.value+=i;
}
helper.apply(this,[i]);//使用apply改变helper的this对象指向,使其指向myNumber对象。当然此处也可以使用call来改变this指向问题
}
}
myNumber.add(1);
第三种方法
var myNumber={
value:1,
add:function(i){
var helper=function(i){
this.value+=i;
}.bind(this,i);//使用bind绑定,和apply相似,只是它返回的是对函数的引用,不会立即执行
helper(i);
}
}
myNumber.add(1);
至于第三种涉及到了bind函数的应用,如果不是特别熟的,请看下面绑定函数
bind():最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值。常见的错误就是将方法从对象中拿出来,然后调用,并且希望this指向原来的对象。如果不做特殊处理,一般会丢失原来的对象。使用bind()方法能够很漂亮的解决这个问题this.num = 9;
var mymodule = { num: 81, getNum: function() { return this.num; } }; module.getNum(); // 81 var getNum = module.getNum; getNum(); // 9, 因为在这个例子中,"this"指向全局对象 // 创建一个'this'绑定到module的函数 var boundGetNum = getNum.bind(module); boundGetNum(); // 81
如果想要更详细的了解绑定函数的用法,建议看Javascript中bind()方法的使用与实现,上面写的很详细
3、闭包需要注意什么?
不要滥用闭包,因为闭包会使得函数中的变量一直保存在内存中,内存的消耗很大
在IE下,使用闭包有可能导致内存泄漏,所以我们在使用闭包的时候要注意,在退出函数之前,将不使用的闭包全部删除
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
讲完闭包我么再了解一下和闭包有关的js中的垃圾回收机制,以及内存泄漏的相关知识
什么是垃圾回收机制?
Js具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。
JS中最常见的垃圾回收方式是标记清除。
工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:
1. 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
2. 去掉环境中的变量以及被环境中的变量引用的变量的标记。
3. 再被加上标记的会被视为准备删除的变量。
4. 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
引用计数 方式
工作原理:跟踪记录每个值被引用的次数。
工作流程:
1. 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。
2. 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1.
3. 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1.
4. 当引用次数变成0时,说明没办法访问这个值了。
5. 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。
但是循环引用的时候就会释放不掉内存。循环引用就是对象A中包含另一个指向对象B的指针,B中也包含一个指向A的引用。
因为IE中的BOM、DOM的实现使用了COM,而COM对象使用的垃圾收集机制是引用计数策略。所以会存在循环引用的问题。
解决:手工断开js对象和DOM之间的链接。赋值为null。IE9把DOM和BOM转换成真正的JS对象了,所以避免了这个问题。
什么是内存泄漏?
简单点说,不再用到的内存,没有及时释放,就叫内存泄漏
当页面跳转的时候,变量不会释放,一直存在于内存当中,然后使你的CPU在累加,在提高,只有当你关闭浏览器的时候,内存才会被释放。
什么情况会引起内存泄漏?
虽然有垃圾回收机制但是我们编写代码操作不当还是会造成内存泄漏。
1. 意外的全局变量引起的内存泄漏。
原因:全局变量,不会被回收。
解决:使用严格模式避免。
2. 闭包引起的内存泄漏
原因:闭包可以维持函数内局部变量,使其得不到释放。
解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。
3. 没有清理的DOM元素引用
原因:虽然别的地方删除了,但是对象中还存在对dom的引用
解决:手动删除。
4. 被遗忘的定时器或者回调
原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。
解决:手动删除定时器和dom。
5. 子元素存在引用引起的内存泄漏
原因:div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。
解决:手动删除清空。
OK,终于整理完毕了,晚安