結論懶人包先奉上~
閉包成立條件:
- 雙層函式 (雙層吉士堡
- 內部函示使用了外部函式的區域變數
結果:
此變數會被 cached 在內部函數,變成內部函數獨有的變數
那就開始囉~
在談閉包之前,讓我們先了解 JS 中的範疇 Scope
What ? 範疇是什麼
廣義的結論:
JS的程式碼片段都必須在執行前編譯好,範疇 ( Scope ) 就是在引擎 ( Enginee )編譯時,查找的規則依據
嚴謹來說:
定義良好的規則來將變數儲存在某些位置,以便在之後找回那些變數;
這組規則,用來規範引擎如何藉由一個變數的識別名稱 ( identifier name)來查找,我們便稱做是範疇From You don’t know JS
一些規則如下:
巢狀範疇
如果在最接近的範疇中找不到變數,Engine 就會往上一層外層查找,直到找到、或是到達最外層(全域)範圍為止。
以下圖為例,將巢狀範疇想像成一棟大樓,目前執行的範疇是位在一樓的 Current Scope,當無法在該樓找到欲找的變數時,便會往二樓查找,以此類推,直到查找變數,或是查找到頂樓 Global Scope 為止。
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
- JavaScript Interpreted or Compiled?
- JavaScript Closure Tutorial — With JS Closure Example Code
內文也有提供一些閉包的範例,可參考。
>> Medium 同步上架