閉包是什麼能吃嗎?

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 同步上架

Javascript 刷 LeetCode 學演算法 – 搜尋 Linear Search, Binary Search

Linear Search

時間複雜度:O(n)



744. Find Smallest Letter Greater Than Target


撞到語法不夠熟悉的問題,原本嘗試在 forEach 中 return 和 break,發現不適用
這種情況適合改用 for loop or every 等等其他方法,可參考以下文章

https://masteringjs.io/tutorials/fundamentals/foreach-break


Binary Search

時間複雜度: O log(n)

適用情境:在一個已排序的陣列中,找出其中特定的值

實作關鍵:遞迴or無窮迴圈,雙指標、中值
=> 設立雙指標、中值,並在查找的過程中不斷移動邊界

367. Valid Perfect Square


延伸閱讀:

取中值的公式

直觀來說會寫

int mid = (low + high)/2;

但這篇文章提到建議改寫

int mid = low + (high – low)/2;

原因是超過最大正整數時,會溢位,這時候取的中值就會是錯的

But if we calculate the middle index like this means our code is not 100% correct, it contains bugs.

That is, it fails for larger values of int variables low and high. Specifically, it fails if the sum of low and high is greater than the maximum positive int value(231 – 1 ).

The sum overflows to a negative value and the value stays negative when divided by 2.


Reference from geeksforgeeks