JavaScript 闭包

在JavaScript中, 闭包(closure)一直是开发者们比较难以理解的概念, 当我们真正理解了JavaScript中关于 执行上下文 的概念后,我们离理解闭包的设计机制就不远了。

闭包与普通的函数区别在于,它绑定了相应的执行环境,JavaScript的闭包主要包括两个部分:

  • 执行上下文的词法环境
  • 表达式

原理

关于闭包, 我想开发者最懵的就是,外部函数都执行完毕并返回了, 为什么内部函数还可以访问到外部函数中定义的变量呢?

为了理清楚闭包的原理, 我们将闭包相关的执行上下文和作用域链梳理一下, 首先看一个简单的闭包示例:

function outterF() {
    var a = 'outter variable';
    return function innerF() {
        console.log(a);
    }
}

var callInnerF= outterF();

callInnerF(); 

程序加载运行后, 首先创建全局执行上下文并推入执行栈中。程序首先创建了一个函数outterF和一个变量callInnerF, 此时全局执行上下文所对应的词法环境如下(略去无关内容):

globalLexicalEnvironment: { 
    outerEnvironmentRef:null, 
    environmentRecord:{
        "outterF":outterF,
        "callInnerF":undefined
    }
},

接下来在outterF()函数执行时候, 则创建了对应的函数执行上下文并推入执行栈。 它所对应的词法环境如下所示:

outterFLexicalEnvironment: { 
    outterEnvironmentRef:globalLexicalEnvironment, 
    environmentRecord:{
        "a":'outter variable',
        "innerF":innerF
    }
}

此时执行栈的栈中包含了outterFContextglobalContext。 每个执行上下文包含了对应的词法环境,函数outterF的词法环境的outterEnvironmentRef引用指向了外层的全局词法环境globalLexicalEnvironment

outterF函数执行过程中创建了innerF函数对象,并作为函数的返回值。 此时的innerF函数并未执行,只是被声明了出来, 函数对象的[[Environment]]属性指向了innerF函数定义时的词法环境:outterFLexicalEnvironment

outterFLexicalEnvironment: { 
    outterEnvironmentRef:globalLexicalEnvironment, 
    environmentRecord:{
        "a":'outter variable',
        "innerF":innerF
    }
}

outterF执行完毕后, 函数执行上下文出栈并销毁。此时的执行栈中只有全局的执行上下文globalContext

虽然outterF函数的执行上下文被销毁,但是它所对应的词法环境outterFLexicalEnvironment却没有被销毁。 因为在innerF函数对象的[[Environment]]属性保存着innerF函数定义时的词法环境——outterFLexicalEnvironment

这里就是理解闭包本身的关键所在。

callInnerF变量指向了outterF函数的返回值 ——innerF函数。因此, 当执行callInnerF()事实上就是调用执行innerF函数。 当callInnerF函数执行,JavaScript引擎会为它创建对应的执行上下文和词法环境,并使用innerF函数的[[Environment]]属性初始化callInnerF函数的词法环境:

callInnerFLexicalEnvironment: { 
    outterEnvironmentRef:outterFLexicalEnvironment, 
    environmentRecord:{}
}

innerF中执行打印a变量操作时, 会沿着outterEnvironmentRef形成的作用域链向外查找,并在outterFLexicalEnvironment所指向的environmentRecord中获取到a变量。

这个过程解释了当outterF函数执行完毕,所对应的执行环境销毁,但由于所对应的变量对象还留在内存中, 我们仍然能够访问到它内部的变量a.

innerF函数执行完毕,它所对应的执行上下文和词法环境才会被销毁。

总结

在上面的分析过程后,我们可以简单地理解了闭包的概念,闭包就是一个内部定义的函数可以访问它外部函数中定义的变量和参数, 即便它的外部函数已经执行完毕并返回了结果。