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
}
}
此时执行栈
的栈中包含了outterFContext
和globalContext
。
每个执行上下文包含了对应的词法环境
,函数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
函数执行完毕,它所对应的执行上下文和词法环境才会被销毁。
总结
在上面的分析过程后,我们可以简单地理解了闭包的概念,闭包就是一个内部定义的函数可以访问它外部函数中定义的变量和参数, 即便它的外部函数已经执行完毕并返回了结果。