# What is closure? ###### tags: `Javascript` `note` Closure 在 Javascript 之中無處不在,但卻難以用簡單白話的方式說明這什麼,多數都是以範例的方式呈現,而大多詳細解說都會涵蓋較艱澀難懂的名詞(例如 Lexical Environment),就連 MDN 也不例外,直到我在 [Eloquent JavaScript](https://eloquentjavascript.net/) 這本書中找到了較為直覺易懂的解釋,再搭配其他教材能讓你更好的理解 closure 怎麼運作。 [Eloquent JavaScript Chapter 3 Functions Closure](https://eloquentjavascript.net/03_functions.html#h_hOd+yVxaku) >This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called ***closure***. A function that references bindings from local scopes around it is called a ***closure***. 「 一個引用其周圍局部作用域的綁定的函數被稱之為 ***closure*** 」。 那何謂**綁定**? **變數**、**函數**、**參數**都可以視為**綁定**。 ```javascript function wrapFn(a){ // argument const b = 10; // variable // function function fn(){ console.log("hello function"); } // arrow function const fn2 = () => { console.log("hello arrow function") }; return () => { const c = b * a; fn(); fn2(); } } const wrap = wrapFn(10); wrap(); ``` 這個 wrap function 就可稱為一個 closure,因為它引用了自己以外的作用域的**綁定**。 而另一個特性是,每當你執行 `wrapFn` 都會產生新的作用域,所以返回的 function 引用的綁定都會是新的。 ### 各自獨立的作用域 我們以最常見的計數器當範例: ```javascript function createCounter(){ let count = 0; return { increment:() => { counter++; }, getCount:() => count } } const counter1 = createCounter(); const counter2 = createCounter(); counter1.increment(); counter1.increment(); counter2.incrament(); counter1.getCount() // 2 counter2.getCount() // 1 ``` 那這有什麼作用? 想想在其他語言有什麼是不能被外部讀取,並且只能由公有方法來操作的東西? 答案就是「 **私有變數/方法** 」,在 **ES11** 之前,JS **class** 並沒有辦法做到真正的私有變數功能,只能依賴開發者的自律性,直到在 [**ES11**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields#syntax) 新增了 `#` 符號表示私有變數或方法。 一般常見的作法是加上標記以利於識別,但其實還是可以直接引用。 ```javascript class Human{ _hands = 2; constructor(){ } } const human = new Human(); console.log(human._hands) // 2 ``` ES11 加上 `#` 前綴,當你直接引用時會發生錯誤 ```javascript class Human{ #hands = 2; constructor(){ } } const human = new Human(); console.log(human.#hands) // syntax error ``` 即便 `class` 有了私有變數功能,在 JS 使用 closure 還是相當常見。 ### Lazy calculation 惰性計算,在 functional programming 中是相當重要且常見的技巧,而因為 JS 有了 closure 特性,使其在 FP 的表現力更上一層樓,讓 function 的**組合**更靈活。 利用 **Lazy calculation** 再產出更多不同的 function: ```javascript function add(a){ return function(b){ return a + b; } } const add10 = add(10); const add20 = add(20); console.log(add10(4)) // 14 console.log(add20(6)) // 26 ``` 利用 **Lazy calculation** 來描述一個流程的每個動作,以 Ramda 為例: ```javascript const process = R.pipe(R.map(n => n * n), R.filter((n) => n % 2 === 0), R.sum); console.log(process([1, 2, 3, 4])) // [1, 2, 3, 4] -> [1, 4, 9, 16] -> [4, 16] -> 20 ``` ### Rust closure 在 Rust,closure 又是什麼規則呢? 其分成**參考**跟**移動所有權**,如果為將一個變數放到 closure function 裡面,都會默認為參考,而在前面加上 `move` 關鍵字則會將**所有權**移入 closure function 內。 因為一般的數字都會做 copy,所以有沒有加 `move` 沒有區別,這裡我將使用 String 作為範例。 這裡因為 print_a 只有對 **a** 做引用,所以第二個 print 並不會出錯,不過要注意的是,如果將 a 作為 print_a 的回傳值,還是會把所有權拿走。 ```rust fn main(){ let a = "hello world".to_string(); let print_a = || { println!("{}", a); }; print_a(); println!("{}", a); } ``` 加上 `move` ```rust fn main(){ let a = "hello world".to_string(); let print_a = move || { println!("{}", a); }; print_a(); println!("{}", a); } ``` 加上 `move` 之後代表我要求將所有變數所有權移入,所以在第二個 print 的 a 的值早已被 print_a 移走,所以會發生錯誤。 這只是一個簡單的範例,實際上 rust closure 的狀況還是相當複雜,所以還是多留意。 ### 參考 讀懂了 Eloquent JavaScript 之後,可以在更進階的去讀其他深入的文章。 - [MDN Closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) - [Javascript.info Lexical Environment](https://javascript.info/closure#lexical-environment)