从《JS 总结之函数、做用域链》一文中知道做用域链的做用,保证了对全部变量对象的有序访问。html
函数外的是没法访问函数内部的变量,有时候要用到怎么办?咱们的主角,闭包就是能够解决这个问题。git
引用 MDN 上的解释:github
闭包是函数和声明该函数的词法环境的组合。闭包
引用 《JavaScript 高级程序设计(第 3 版)》上的解释:函数
闭包是指有权访问另外一个函数做用域中的变量的函数。ui
这两个解释都在说着同一件事,闭包能访问声明时函数所在的环境中的变量和函数。this
那具体是由于什么才会让闭包访问到环境中的变量和函数,这得靠两兄弟:变量对象和做用域链。spa
变量对象翻译
当执行函数的时候,会建立和初始化一个函数执行环境,在函数执行环境中,全局执行环境的变量对象(Variable Object,缩写为 VO)不能直接访问,此时由激活对象(Activation Object,缩写为 AO)扮演 VO 的角色。设计
变量对象专门用来梳理和记住这些变量和函数,是做用域链造成的前置条件 。但咱们没法直接使用这个变量对象,该对象主要是给 JS 引擎使用的。具体能够查看《JS 总结之变量对象》。
变量对象就至关于一个存放的仓库,获取到里面的东西,还得须要去获取这些的路径,这就是做用域链的事情了。
做用域链
然而,光有变量对象可完成不了闭包的造成,怎样才能让函数访问到,这得靠做用域链,做用域链的做用就是让函数找到变量对象里面的变量和函数。具体能够查看《JS 总结之函数、做用域链》
虽然都是讲闭包,但 MDN 上面讲的是声明该函数的词法环境,而 JS 高程讲的是访问另外一个函数做用域中,从解释上的不一样,闭包便有了理论中的闭包( MDN )和实践中的闭包( JS 高程)之分。
根据 MDN 的解释写个例子:
var a = 1
function fn() {
console.log(a)
}
fn()
复制代码
函数 fn 和函数 fn 的词法做用域构成了一个闭包。可是这不是普通的函数吗?
在《JavaScript 权威指南》中也获得证明:
从技术的角度讲,全部 JavaScript 函数都是闭包
汤姆大叔翻译的文章中讲,实践中的闭包须要知足如下两个条件:
什么是上下文?即函数的执行环境。
什么是自由变量?即函数的词法做用域中的变量和函数,而不是函数自己的参数或者局部变量,或者说是所在函数的变量对象中的变量和函数。
这两点和 JS 高程中讲的闭包的解释不谋而合。
如今写个符合的例子:
function fn() {
var a = 1
function fn1() {
console.log(a)
}
return fn1
}
var b = fn()
b()
复制代码
当执行 b 的时候,建立它的执行环境 fn 早已经摧毁,但函数 b 还能访问到变量 a。
好吧,我有点乱!
要完全明白这个是咋回事,要结合执行环境、活动变量和做用域链来看,让咱们来看看这个例子的执行过程:
环境栈 = [globalContext]
复制代码
globalContext = {
VO: [global],
Scope: [globalContext.VO],
this: globalContext.VO
}
复制代码
全局执行环境的做用域链
到 fn 的 [[scope]]
属性fn.[[scope]] = [
globalContext.VO
]
复制代码
环境栈 = [fnContext, globalContext]
复制代码
[[scope]]
建立 fn 做用域链fnContext = {
AO: {
arguments: {},
scope: undefined,
fn1: reference to function fn1(){}
},
Scope: [AO, globalContext.VO],
this: undefined
}
复制代码
fnContext 的做用域链
到 fn1 的 [[scope]]
属性fn1.[[scope]] = [
fnContext.AO, globalContext.VO
]
复制代码
环境栈 = [globalContext]
复制代码
环境栈 = [
fn1Context,
globalContext
]
复制代码
[[scope]]
建立 fn1 做用域链fn1Context = {
AO: {
arguments: {}
},
Scope: [AO, fnContext.AO, globalContext.VO],
this: undefined
}
复制代码
环境栈 = [globalContext]
复制代码
当执行函数 b 的时候,建立它的执行环境 fn 早已摧毁(步骤 6),只留下了它的活动变量 fnContext.AO
于内存中(步骤 5):
fn1Context = {
Scope: [AO, fnContext.AO, globalContext.VO]
}
复制代码
fnContext.AO
存在 fn1 的做用域链中,因此能访问到fn1 的词法环境,这便造成了闭包。所以,闭包是变量对象和做用域链共同做用的结果。