什么是闭包
在<<你不知道的JavaScript>>中解释到: 当函数可以记住并访问所在的词法作用域时,就产生了闭包
变量作用域
因为闭包就是基于变量的作用域
变量的作用域
- 全局变量
- 局部变量
变量使用
局部函数中使用全局变量
var temp = 1;
function fn(){
console.log(temp)
}
fn()
js可以在局部函数中使用全局作用域的变量 在内部函数fn中可以使用全局变量temp
全局作用域中使用函数中的局部变量
function fn(){
var temp = 1
}
console.log(temp)
- 执行结果为:Uncaught ReferenceError: temp is not defined at xxx.html 报错.
- 所以在函数的外部是无法使用函数中的局部变量
- 注意,在局部函数中定义变量要使用var 关键字,否则,会定义为全局变量
function fn(){
temp = 1
}
fn()
console.log(temp)
这样就可以打印temp的值了
使用局部变量
如果想使用函数中的局部变量,那么就在函数的内部在定义一个函数,并返回
function f1(){
var temp = 'hello';
function f2(){
console.log(temp);
}
return f2
}
f1()()
- f2定义在f1函数的内部,所以f2函数中可以使用f1内部的局部变量(变量查找是一层一层往上找的)
- 所以当执行f2函数的时候是可以打印f1中变量temp
闭包
- 首先有两个函数,为父子关系
- 子函数引用父函数的变量
- 执行函数定义就会产生闭包(不需要调用内部函数)

演示闭包

闭包的生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了闭包,而不是调用时(就是刚进入外部函数内部的时候就产生了闭包,因为函数提升,如果是变量接收方式定义内部函数的话,就是在执行该行代码时产生)
- 死亡: 在嵌套的内部函数成为垃圾对象时,xxx=null时
闭包的用途
- 可以使用函数内的局部变量
- 这些变量可以始终保持在内存中
- 如果使用了单例模式,可以用到闭包
function f1() {
var temp = 100
function f2() {
console.log(temp)
}
add = function(){
temp += 1
}
return f2
}
var result = f1()
result()
add()
result()
result = null //释放全局变量,会销毁常驻内存的局部变量
- 在函数f1额外定义了一个全局回调函数,并将temp+= 1
- 调用f2时,打印100
- 执行了add() temp+=1
- 在调用了f2,打印了101
- 如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收
- 如果两个对象互相引用,不与第三个对象产生关系,那么这两个对象也会被垃圾回收
- 当不使用的对象赋值null操作,释放
这说明了,当f2被调用的时候,闭包并没有被清除,变量仍然存在于内存中,给他进行+1操作,是在原变量的基础上增加
- 因为闭包是父子嵌套关系,当子函数返回并用一个全局变量接收,所以子函数会始终在内存中,而子函数又依赖于父函数,所以父函数也一直在内存中,所以不会在调用之后被垃圾回收机制回收
for循环问题
看一下下面代码的结果
function test(){
var arr = [];
for(var i = 0;i < 10;i++){
arr[i] = function(){
return i;
};
}
for(var a = 0;a < 10;a++){
console.log(arr[a]());
}
}
test();
- 当打印的时候发现打印了10 个 10 ,这并不是我们想象的那种结果
但是换一种方式
function test(){
var arr = [];
for(let i = 0;i < 10;i++){
arr[i] = function(){
return i;
};
}
for(var a = 0;a < 10;a++){
console.log(arr[a]());
}
}
test();
- 只是把for循环中的var 换成了let,结果就是我们想象的那样了(0,1,2,...9)
这是为什么
- 这个跟var 和let 的块作用域有关,在es5中,for循环中的var并不代表着一个块作用域,所以for循环中用var定义的是一个全局变量,或者是for循环外函数的局部变量,当循环结束,变量的值自然而然的变成了10
- 但是let就不一样了,也会将for作为一个块作用域(其实是离let最近的{},let可以绑定到任意块作用域上),也就是说for循环内部与外部不在同一个作用域内
建议
- 正常函数的作用域或者其他变量的都会在函数执行之后被销毁,但是创建了闭包会使局部变量长期保存在内存中,消耗内存很大,所以不能滥用,容易导致内训泄露或性能问题,但是可以在退出函数时将不使用的局部变量删除(将 f1 = null... 处理)
- 闭包会在父函数外部,改变内部变量的值,所以存在操作失误的可能
阮大神的两个思考题
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
解析
- 闭包中引用了this指针
- 因为闭包会赋值给全局变量,所以那时this指向的其实是window对象
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
解析
- 跟上面的区别就是将this赋值给了局部变量,
- 因为闭包,所以只有在函数内部可以使用,当调用的的时候依然是函数内部的变量
一道经典的面试题
function fun(n,o){
console.log(o);
return {
fun: function(m){
return fun(m,n);
}
};
}
//n = 0 o为undefined
var a = fun(0);
// m = 1 n = 0 o为0
a.fun(1);
//n没有变化 依然是0
a.fun(2);
//n没有变化 依然是0
a.fun(3);
//undefined,0,1,2
//这是个难点:当fun(0) => n=0,o=undefined,当.fun(1)=> n=0,m=1,o=0,.fun(2)=> m=2,n=1,o=1,...
var b = fun(0).fun(1).fun(2).fun(3);
//undefined,0
var c = fun(0).fun(1);
//1
c.fun(2);
c.fun(3);