閉包是什麼能吃嗎?

Photo by Valeria Boltneva on Pexels.com

結論懶人包先奉上~

閉包成立條件:

  1. 雙層函式 (雙層吉士堡
  2. 內部函示使用了外部函式的區域變數

結果:

此變數會被 cached 在內部函數,變成內部函數獨有的變數


那就開始囉~
在談閉包之前,讓我們先了解 JS 中的範疇 Scope

What ? 範疇是什麼

廣義的結論:
JS的程式碼片段都必須在執行前編譯好,範疇 ( Scope ) 就是在引擎 ( Enginee )編譯時,查找的規則依據

嚴謹來說:

定義良好的規則來將變數儲存在某些位置,以便在之後找回那些變數;
這組規則,用來規範引擎如何藉由一個變數的識別名稱 ( identifier name)來查找,我們便稱做是範疇

From You don’t know JS

一些規則如下:

巢狀範疇

如果在最接近的範疇中找不到變數,Engine 就會往上一層外層查找,直到找到、或是到達最外層(全域)範圍為止。

以下圖為例,將巢狀範疇想像成一棟大樓,目前執行的範疇是位在一樓的 Current Scope,當無法在該樓找到欲找的變數時,便會往二樓查找,以此類推,直到查找變數,或是查找到頂樓 Global Scope 為止。

視覺化巢狀範圍,圖片來自 You don’t know JS

Who ? 誰是範疇

  • 函式:
    Js 具有以函式為基礎的範疇,宣告的每一個函式都會為自身建立一個泡泡,將其中的資訊隱藏起來,不讓外層範疇存取。
  • 區塊 block-scoping:
    ex: for , if block 中
for(let i = 0; i ≤nums; i++){
block-scoping, i 只存在於這區塊間
}

靠近被使用的地方,再宣告變數,盡可能的本地化。

  • let
    宣告時,便接附 ( attaches to )上它所在的區域。
if(true) {
let bar = 'hi123'
console.log('inner bar',bar) //hi123}
console.log('outer bar',bar) //ReferenceError

*不會被拉升到現有區塊都能看見,因此程式未執行到宣告述句前,此變數不算存在。

{
 console.log('bar',bar) //ReferenceError
 let bar = 'woo'
}



回到正題,閉包是函式記得並存取語彙範疇的能力;對於語彙範疇一詞,感到陌生的,可以先去看以下文章,其中會提到JS 編譯過程。

語彙範疇( lexical scoping ):語意分析 (Parsing) 時期定義的範疇

[You don’t know JS]來談談 Hosting 拉升,是在拉什麼?

另外我也參考了,其他文章所描述的語彙範疇:

So, lexical or static scoping means the scope and value of a variable is determined from where it is defined. It doesn’t change.

reference from JavaScript Closure Tutorial — With JS Closure Example Code

也就是,範疇與變數的值在定義、創造時(編譯時期)便決定了,後續不會被改變。

函式即使在其編寫時期的語彙範疇之外被調用,閉包讓此函式仍能繼續存取它在編寫時期定義處的那個語彙範疇。所以用任何方式將內層函示運送到其語彙範疇外,它依仍會保留其原本宣告處的範疇參考(條件一)

function foo() {
var a = 2;
function bar(){
console.log(a)
}
return bar
}
var baz = foo()
baz()

bar() 被執行了,是在其宣告處的語彙範疇之外執行的。
照理說,foo()執行完後,看似不會再被使用,一般來說我們會預期整個內層範疇都會被釋放,但由於內層 bar() 函式仍還在使用此範疇,仍有一個參考(變數 a) 指向 foo() 範疇(條件二),因此此範疇得以仍存活。


最後再回顧一下,我們一開始提到的

閉包成立條件:

條件一:雙層函式

條件二:內部函示使用了外部函式的區域變數

ok,收工~


Learn More

  1. JavaScript Interpreted or Compiled?
  2. JavaScript Closure Tutorial — With JS Closure Example Code
    內文也有提供一些閉包的範例,可參考。

>> Medium 同步上架