闭包的概念
我的理解为,就是函数嵌套函数,内部的函数可以访问到外部函数申明的变量和参数(闭包是函数里面的函数,并且它能记住周围的东西。)
特点:
1、让外部访问函数内部变量成为可能;
2、 局部变量会常驻在内存中;
3、 可以避免使用全局变量,防止全局变量污染;
4、会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
闭包的创建
闭包可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。
闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。
但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。
应用场景
例1
function f1(){
var n = 999;
function f2(){
console.log(n);
}
return f2;
}
var result=f1();
result(); // 999
这个比较简单,f2()为闭包函数,可访问fI()作用域下面的n
例2
function outerFn(){
var i = 0;
function innerFn(){
i++;
console.log(i);
}
return innerFn;
}
var inner = outerFn(); //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
var inner2 = outerFn();
inner(); // 1
inner2(); // 1
inner2(); // 2
inner(); // 2
inner(); // 3
每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。所以说虽然inner和inner2都是innerFn(),但是都创建了新地址,都是自己的,互不干扰。
例3
var i = 0;
function outerFn(){
function innnerFn(){
i++;
console.log(i);
}
return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); //1 2 3 4
变量 i是在outerFn外部声明的,是全局变量,所以引用的是同一个地址
例4
function fn(){
var a = 3;
return function(){
return ++a;
}
}
alert(fn()()); //4
alert(fn()()); //4
var newFn = fn();
console.log(newFn());//4
console.log(newFn());//5
console.log(newFn());//6
fn()()的直接调用都是在开辟一个新的地址,但是把它赋给一个变量的时候,地址就不会再改变了,这个时候就会发现输出只是在递增的。
例5
(function() {
var m = 0;
function getM(){
return m;
}
function seta(val){
m = val;
}
window.g = getM;
window.f = seta;
})();
f(100);
console.log(g());//100
(function(){ /* code */ })()是立即执行函数,当js执行到(function {// code})();时, 由于(function {// code})是表达式, js会去对它求解得到返回值, 由于返回值是一个函数, 故而遇到()时, 便会被执行。然后把getM和seta给到了window,这时候呢,我想到了大佬总结的一句话:闭包找到的是同一地址中父级函数中对应变量最终的值。
例6
function a() {
var i = 0;
function b() {
console.log(++i);
}
return b;
}
var c = a();
c(); //1
c(); //2
注意。打印的是++i,是先加后返回,如果是i++,则返回的结果是 0,1
例7
function fn(){
var num = 223;
var fn2 = function() {
console.log(num);
}
num++;
return fn2;
}
var f1= fn();
fn(); //输出224
例8
function fun(n,o) {
console.log(o);
return {
fun:function(m) {
return fun(m,n);
}
};
}
var a = fun(0); //undefined
a.fun(1); //0
a.fun(2); //0
a.fun(3); //0
var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
var c = fun(0).fun(1);
c.fun(2);
c.fun(3); //undefined 0 1 1
变量a
第一个fun(0)是在调用第一层fun函数。第二个fun(1)是在调用前一个fun的返回值的fun函数,所以:
第后面几个fun(1),fun(2),fun(3),函数都是在调用第二层fun函数。
在第一次调用fun(0)时,o为undefined;
第二次调用fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
第三次调用fun(2)时m为2,但依然是调用a.fun,所以还是闭包了第一次调用时的n,所以内部调用第一层的fun(2,0);所以o为0
第四次同理;
即:最终答案为undefined,0,0,0
变量b
先从fun(0)开始看,肯定是调用的第一层fun函数;而他的返回值是一个对象,所以第二个fun(1)调用的是第二层fun函数,后面几个也是调用的第二层fun函数。
在第一次调用第一层fun(0)时,o为undefined;
第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
第三次调用 .fun(2)时m为2,此时当前的fun函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次执行第一层fun函数时时(1,0)所以n=1,o=0,返回时闭包了第二次的n,遂在第三次调用第三层fun函数时m=2,n=1,即调用第一层fun函数fun(2,1),所以o为1;
第四次调用 .fun(3)时m为3,闭包了第三次调用的n,同理,最终调用第一层fun函数为fun(3,2);所以o为2;
即最终答案:undefined,0,1,2、
变量c
根据前面两个例子,可以得知:
fun(0)为执行第一层fun函数,.fun(1)执行的是fun(0)返回的第二层fun函数,这里语句结束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,所以c中闭包的也是fun(1)第二次执行的n的值。c.fun(2)执行的是fun(1)返回的第二层fun函数,c.fun(3)执行的也是fun(1)返回的第二层fun函数。
在第一次调用第一层fun(0)时,o为undefined;
第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
第三次调用 .fun(2)时m为2,此时fun闭包的是第二次调用的n=1,即m=2,n=1,并在内部调用第一层fun函数fun(2,1);所以o为1;
第四次.fun(3)时同理,但依然是调用的第二次的返回值,遂最终调用第一层fun函数fun(3,1),所以o还为1
即最终答案:undefined,0,1,1
例9
function fn(){
var arr = [];
for(var i = 0;i < 5;i ++){
arr[i] = function(){
return i;
}
}
return arr;
}
var list = fn();
for(var i = 0,len = list.length;i < len ; i ++){
console.log(list[i]());
} //5 5 5 5 5
看样子函数打印对应数字,1,2,3,4,5 实际不是,因为每个闭包函数访问变量i是fn执行环境下的变量i,随着循环的结束,i已经变成5了,所以执行每个闭包函数,结果打印5,5
怎么解决这个问题呢?看下面例子
例10
function fn(){
var arr = [];
for(var i = 0;i < 5;i ++){
arr[i] = (function(i){
return function (){
return i;
};
})(i);
}
return arr;
}
var list = fn();
for(var i = 0,len = list.length;i < len ; i ++){
console.log(list[i]());
} //0 1 2 3 4
使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。