词法环境
词法环境(lexical environment)由两个部分组成:
- 环境记录——一个存储所有局部变量作为其属性的对象。
- 对外部词法环境的引用,与外部代码相关联。
全局词法环境在脚本执行前创建,它没有更外层的词法环境。
// 全局词法环境
let a = ‘hello‘ // => hello
let b // => undefined
b = ‘world‘ // => world
a
和b
作为环境记录这一对象的属性,它们被声明时就被赋予值或在之后的过程中被赋予值。
let who = ‘小明‘
function say(content) {
let date = new Date()
alert(`${who}说:${content} | at ${date}`)
}
say(‘你好,世界!‘)
say()
函数能够构成一个词法环境,因为函数引用了外部词法环境(也就是环境记录的属性who)。say()
函数访问外部词法环境时,首先会搜索内部词法环境,然后搜索外部词法环境,然后搜索更外部的词法环境,以此类推,直到全局词法环境。
闭包
一个函数和其词法环境的组合就是闭包。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。ps:该定义被简化,原话在闭包 - JavaScript | MDN。
首先我们看一个简单计数器案例:
function count() {
let record = 0
return record++
}
let v = 0
v = count()
v = count()
console.log(v) // 0
我们发现执行两次count()
函数,其返回的结果依旧是0。
在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联,它维持了一个环境,可以持续操作:
let counter = {
_record: 0,
set count(record) {
this._record = record
},
get count() {
return this._record++
}
}
let v = 0
v = counter.count
v = counter.count
console.log(v) // 1
如果利用闭包进行计数器操作,是否能够实现计数器案例?
function count() {
let record = 0
return function f() {
return record++
}
}
let v = count()
console.log(v()) // 0
console.log(v()) // 1
利用闭包成功实现了计数器案例。
v
是执行count()
时创建的f()
函数的引用。v
维持了一个词法环境。因此,当count()
被多次调用时,变量record
仍然可用,可以持续操作。
f()
函数和其词法环境是一个闭包,或者将视野往上看,count()
函数的作用域下就是一个闭包,内层函数f()
在闭包的作用下,可以访问外层函数的作用域内的任何局部变量。
最后,在《JavaScript权威指南》中指出“所有JavaScript函数都是闭包”。