詞法環境
詞法環境(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函式都是閉包”。