JavaScript-闭包

686 阅读5分钟

什么是闭包

在<<你不知道的JavaScript>>中解释到: 当函数可以记住并访问所在的词法作用域时,就产生了闭包


变量作用域

因为闭包就是基于变量的作用域

变量的作用域

  1. 全局变量
  2. 局部变量

变量使用

局部函数中使用全局变量

var temp = 1;
function fn(){
    console.log(temp)
}
fn()

js可以在局部函数中使用全局作用域的变量 在内部函数fn中可以使用全局变量temp

全局作用域中使用函数中的局部变量

function fn(){
    var temp = 1 
}
console.log(temp)
  1. 执行结果为:Uncaught ReferenceError: temp is not defined at xxx.html 报错.
  2. 所以在函数的外部是无法使用函数中的局部变量
  3. 注意,在局部函数中定义变量要使用var 关键字,否则,会定义为全局变量
    function fn(){
        temp = 1 
    }
    fn()
    console.log(temp)

这样就可以打印temp的值了

使用局部变量

如果想使用函数中的局部变量,那么就在函数的内部在定义一个函数,并返回

function f1(){
    var temp = 'hello';
    function f2(){
        console.log(temp); 
    }
    return f2
}
f1()()
  1. f2定义在f1函数的内部,所以f2函数中可以使用f1内部的局部变量(变量查找是一层一层往上找的)
  2. 所以当执行f2函数的时候是可以打印f1中变量temp

闭包

  1. 首先有两个函数,为父子关系
  2. 子函数引用父函数的变量
  3. 执行函数定义就会产生闭包(不需要调用内部函数)

当一个嵌套的内部函数引用了嵌套的外部函数的变量时,就产生了闭包

演示闭包

闭包的生命周期

  1. 产生: 在嵌套内部函数定义执行完时就产生了闭包,而不是调用时(就是刚进入外部函数内部的时候就产生了闭包,因为函数提升,如果是变量接收方式定义内部函数的话,就是在执行该行代码时产生)
  2. 死亡: 在嵌套的内部函数成为垃圾对象时,xxx=null时

闭包的用途

  1. 可以使用函数内的局部变量
  2. 这些变量可以始终保持在内存中
  3. 如果使用了单例模式,可以用到闭包
function f1() {
    var temp = 100
    function f2() {
        console.log(temp)
    }
    add = function(){
        temp += 1
    }
    return f2
}

var result = f1()
result()
add()
result()
result = null //释放全局变量,会销毁常驻内存的局部变量
  1. 在函数f1额外定义了一个全局回调函数,并将temp+= 1
  2. 调用f2时,打印100
  3. 执行了add() temp+=1
  4. 在调用了f2,打印了101
  5. 如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收
  6. 如果两个对象互相引用,不与第三个对象产生关系,那么这两个对象也会被垃圾回收
  7. 当不使用的对象赋值null操作,释放

这说明了,当f2被调用的时候,闭包并没有被清除,变量仍然存在于内存中,给他进行+1操作,是在原变量的基础上增加

  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();
  1. 当打印的时候发现打印了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(); 
  1. 只是把for循环中的var 换成了let,结果就是我们想象的那样了(0,1,2,...9)

这是为什么

  1. 这个跟var 和let 的块作用域有关,在es5中,for循环中的var并不代表着一个块作用域,所以for循环中用var定义的是一个全局变量,或者是for循环外函数的局部变量,当循环结束,变量的值自然而然的变成了10
  2. 但是let就不一样了,也会将for作为一个块作用域(其实是离let最近的{},let可以绑定到任意块作用域上),也就是说for循环内部与外部不在同一个作用域内

建议

  1. 正常函数的作用域或者其他变量的都会在函数执行之后被销毁,但是创建了闭包会使局部变量长期保存在内存中,消耗内存很大,所以不能滥用,容易导致内训泄露或性能问题,但是可以在退出函数时将不使用的局部变量删除(将 f1 = null... 处理)
  2. 闭包会在父函数外部,改变内部变量的值,所以存在操作失误的可能

阮大神的两个思考题

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()());

解析

  1. 闭包中引用了this指针
  2. 因为闭包会赋值给全局变量,所以那时this指向的其实是window对象
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());

解析

  1. 跟上面的区别就是将this赋值给了局部变量,
  2. 因为闭包,所以只有在函数内部可以使用,当调用的的时候依然是函数内部的变量

一道经典的面试题

    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);