[JS]閉包和詞法環境

shiramashiro 發表於 2021-07-27

詞法環境

詞法環境(lexical environment)由兩個部分組成:

  1. 環境記錄——一個儲存所有區域性變數作為其屬性的物件。
  2. 對外部詞法環境的引用,與外部程式碼相關聯。

全域性詞法環境在指令碼執行前建立,它沒有更外層的詞法環境。

// 全域性詞法環境
let a = 'hello' // => hello
let b // => undefined
b = 'world' // => world

ab作為環境記錄這一物件的屬性,它們被宣告時就被賦予值或在之後的過程中被賦予值。

[JS]閉包和詞法環境

let who = '小明'
function say(content) {
    let date = new Date()
    alert(`${who}說:${content} | at ${date}`)
}
say('你好,世界!')

say()函式能夠構成一個詞法環境,因為函式引用了外部詞法環境(也就是環境記錄的屬性who)。say()函式訪問外部詞法環境時,首先會搜尋內部詞法環境,然後搜尋外部詞法環境,然後搜尋更外部的詞法環境,以此類推,直到全域性詞法環境。

[JS]閉包和詞法環境

閉包

一個函式和其詞法環境的組合就是閉包。也就是說,閉包讓你可以在一個內層函式中訪問到其外層函式的作用域。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仍然可用,可以持續操作。

[JS]閉包和詞法環境

f()函式和其詞法環境是一個閉包,或者將視野往上看,count()函式的作用域下就是一個閉包,內層函式f()在閉包的作用下,可以訪問外層函式的作用域內的任何區域性變數。

最後,在《JavaScript權威指南》中指出“所有JavaScript函式都是閉包”。