一、闭包
1、概念
闭包——形成私有作用域,让私有变量存到内存里面,保护私有变量,让其不受外界的影响。(外层函数的调用结果是返回内层函数,即内层函数自执行,或者在外层函数作用域内设置返回值为内层函数,这个内层函数就形成闭包,调用外层函数的变量(此时已成为它的私有变量,存到内存里,不受外界影响),形成私有作用域)
闭包的作用:使在外层作用域下可以访问到内层作用域下的变量
闭包的弊端:不建议大量使用闭包,闭包的变量存到内存里边,会导致内存泄漏,或者页面的性能问题。
解决方案:在闭包使用完之后将私有变量删除delete(适用于IE浏览器),或将闭包对应的变量置空(适用于大多数现代浏览器),如下例中的 result=null;,或移除私有变量remove。
什么是内存泄漏?
内存泄漏是指内存资源得不到释放 && 失去对该内存区的指针 => 无法复用内存资源,最终导致内存溢出。
简单来说就是:不再用到的内存,没有及时释放。
什么是js垃圾回收机制?
通常情况下,垃圾回收分为手动回收和自动回收。
手动回收策略,何时分配内存、何时销毁内存都是由代码控制的。
自动回收策略,产生的垃圾数据是由垃圾回收器来释放的,并不需要手动通过代码来释放。
JavaScript 中调用栈中的数据回收
JavaScript 引擎会通过向下移动 ESP(记录当前执行状态的指针) 来销毁该函数保存在栈中的执行上下文。
JavaScript 堆中的数据回收
在 V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。
新生区通常只支持 1~8M 的容量,而老生区支持的容量就大很多了。对于这两块区域,V8 分别使用两个不同的垃圾回收器,以便更高效地实施垃圾回收。
副垃圾回收器,主要负责新生代的垃圾回收。
主垃圾回收器,主要负责老生代的垃圾回收。
点击 Chrome 浏览器垃圾回收机制与内存泄漏分析 了解更多。
2、理解闭包
以下是三个简单的闭包,帮助理解闭包的含义:
示例一:
function fn1() {
var b = 234;
function fn2() { //fn2是一个闭包
console.log(b);
}
return fn2;
}
var result = fn1();
result(); //234 调用result,即执行函数fn2(闭包)
result=null;
//result(); //Uncaught TypeError: result is not a function
说明:result 指向的是fn1的返回值,即函数fn2,声明变量result相当于定义了一个闭包,b为私有变量,存到内存里边,不受外界影响,调用变量result时b的值不会初始化。
示例二:
var add = (function () {
var count = 0; //count是私有变量
return function () { //这个匿名函数是一个闭包
count++;
console.log(count);
}
})();
add(); //1 add指向的是自执行函数的返回字值,匿名函数,执行add,即执行匿名函数(闭包)
add(); //2
add(); //3
示例三:
var med="like";
var fun1=function (){
console.log(med); //undefined 变量的声明提前
var med="windows"; //私有变量
return function (){ //闭包
console.log(med);
med+=" more";
}
};
var ab=fun1(); //相当于将变量ab定义为上边的闭包
fun1()(); //windows 第一次调用时局部变量med(也是闭包的私有变量)会存到堆内存里,输出结果为windows
fun1()(); //windows 第一次执行完fun1时堆内存会被释放,第二次相当于重新执行fun1,形成新的堆内存,所以局部变量med会被初始化
fun1()(); //windows
ab(); //windows
ab(); //windows more 调用的是闭包,fun1只执行一次,所以局部变量med不会被初始化,会累加
ab(); //windows more more
反复调用外部的函数,相当于在重置闭包的变量。
3、闭包案例
案例一:
function fun2(n,o){
console.log(o);
var md=n;
return{
fun2:function(m){
return fun2(m,md);
}
};
}
var a = fun2(0); a.fun2(1); a.fun2(2); a.fun2(3); //undefined 0 0 0
var b = fun2(0).fun2(1).fun2(2).fun2(3); //undefined 0 1 2
var c = fun2(0).fun2(1); c.fun2(2); c.fun2(3); //undefined 0 1 1
注意:
1、函数形参没传值,形参值undefined
2、上例里面存在闭包,也存在对象之间的调用,要注意是对象点,还是对象连点
3、对象点,局部变量会初始化,因为函数执行完,它的执行上下文会被回收,对象点相当于执行完函数,第二次再重新执行。
4、对象连点,局部变量不会初始化,因为对象连点表示函数一直处于执行中,执行上下文不会被回收,直至最后一个点执行完成。
案例二:
var res = [];
for (var i = 0; i < 10; i++) {
var add1 = function () {
return function () {
console.log(i); //当前函数内部输出的变量不是私有变量,而是常规的局部变量,会受外界的影响
}
};
res[i] = add1(); //数组指向返回的匿名函数
}
//在这里运行的时候for循环已经执行完了,所以输出i,全是10
res[0]();
res[1]();
上例不是闭包,输出数组里边的任何一个函数执行都是10
var res=[];
for(var i=0;i<10;i++){
var add=function(){
return function(){
console.log(i);
}
};
res[i]=add();
res[i]();
}
在for循环内部执行
res[i](),返回的结果是0——9,因为每循环一次,就立即执行返回的匿名函数
var res=[];
for(var i=0;i<10;i++){
var add=function(n){
return function(){
console.log(n)
}
};
res[i]=add(i);
}
res[0](); //0
res[1](); //1
res[2](); //2
改为闭包后如上所示,返回函数的执行结果不受for循环影响
案例三:
for循环、计时器的闭包:
for(var i=0;i<10;i++){
setTimeout(function (){
console.log(i); //10个10
},0); //这个计时器是延迟0ms,去执行内部的匿名函数
}
js 是单线程的
setTimeout在当前执行队列的最后执行,所以获取到的i是最外层作用域的i=10
若要获得0——9,需要在for循环内创建闭包
改为闭包:
for (var i=0;i<10;i++){
(function (n){
setTimeout(function (){
console.log(n); //0——9
},1000);
})(i);
}
将 i 作为实参,传入自执行函数,从而变为 setTimeout 里边的自执行函数的私有变量,让其不受外部for循环的影响。
案例四:
事件的闭包:
<button class="btn">1</button>
<button class="btn">2</button>
<button class="btn">3</button>
<button class="btn">4</button>
<script>
var btn=document.querySelectorAll(".btn");
for(var i=0;i<btn.length;i++)
{
(function (n){
btn[n].onclick=function (){
console.log(n);
}
})(i);
//自动触发对象的事件,加上下边这行代码,可以不使用闭包,点击输出对应的0 1 2 3
//btn[i].click();
}
</script>
上例如果不使用闭包,输出的结果全部是4,因为事件是被动执行的,事件触发时for循环已执行完毕,所以输出结果为4。
加上闭包之后就会将 i 变为私有变量,存到内存里,点击时输出对应的 i 即0 1 2 3
本文详细解析了JavaScript中闭包的概念、作用与弊端,通过多个示例帮助理解闭包的形成与应用,同时探讨了内存泄漏与垃圾回收机制,以及如何有效避免闭包导致的性能问题。

498

被折叠的 条评论
为什么被折叠?



