JS的闭合(Closure)

 

定义

MDN:函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。

JS高程: 闭包是指有权访问另一个函数作用域中的变量的函数。

Javascript权威指南:函数对象可以通过作用域关联起来,函数体内的变量都可以保存在函数作用域内,这在计算机科学文献中称为“闭包”,所有的javascirpt函数都是闭包

你不知道的JS(上):当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前作用域外执行

浏览器原理与实践:当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。

各种书籍上对闭包的定义各不相同,但是我们没必要太过于纠结概念,只要我们深入理解他的含义和使用就可以了

产生原因

闭包产生与如下两个方面有关

  • 根据词法作用域的规则,内部作用域可以访问外部作用域
  • GC(垃圾回收)机制,如果变量被引用那么GC在回收时并不会回收该变量

实例

    function foo() { 
        let test1 = '变量1'
        const test2 = '变量2'
        let test3 = '变量3'
        var innerBar = { 
            getName: function () { 
                console.log(test1) 
                return test2 
            },
        } 
        return innerBar 
    } 
    var bar = foo()
    bar.getName()

我们可以通过控制台的Source中的scope看到闭包

右边 Scope 项就体现出了作用域链的情况:Local 就是当前的 getName 函数的作用域,Closure(foo) 是指 foo 函数的闭包,最下面的 Global 就是指全局作用域,从“Local–>Closure(foo)–>Global”就是一个完整的作用域链。

注意⚠️:只有我们在内部函数中使用的变量才会被加入闭包(Closure)中。

我们来看下范例中产生闭包时执行栈的情况:

  • 当执行到 foo 函数内部的return innerBar这行代码时调用栈的情况

  • 当 foo 函数执行完成之后,其整个调用栈的状态(注意⚠️:此时foo执行上下文已出栈)

  • 当执行到bar.getName()时的栈状态

通过上面三个图我们可以清晰的看到当前作用域链 是Local–>Closure(foo)–>Global。

用途

实现私有变量

function Animal( ){

    //私有变量
    var series = "哺乳动物"function run( ){
        console.log("Run!!!");
    }   

    //特权方法
    this.getSeries = function(){
        return series;
    };
}   

实现模块模式

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})();

console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

缺陷

如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。(如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存)