> For the complete documentation index, see [llms.txt](https://mistborn.gitbook.io/til-coding/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mistborn.gitbook.io/til-coding/javascript/closure.md).

# Closure

## A couple of questions

```javascript
// 函式使用外部變數，會使用最新的值嗎?
let name = "John";
function sayHi() {
  alert("Hi, " + name);
}
name = "Pete";
sayHi(); // what will it show: "John" or "Pete"?

// 函式會使用內部變數還是外部變數?
function makeWorker() {
  let name = "Pete";

  return function() {
    alert(name);
  };
}

let name = "John";

// create a function
let work = makeWorker();

// call it
work(); // what will it show? "Pete" (name where created) or "John" (name where called)?
```

## Lexical Environment

在 JavaScript 裡，每個函式、{...}、整個程式碼都有內部物件叫 *Lexical Environment。有 2 個部分*

* &#x20;*Environment Record* 一個物件儲存所有區域變數當作屬性
* &#x20;*指向外部 lexical environment*

變數只是一個 *Environment Record 的屬性，取得或改變變數意思是取得或改變物件的屬性。*

![](/files/-LhdqUEYXE2NSYSKcQPt)

![](/files/-Lhds101ggKkytcdc6Io)

### Function Declaration

函式在 *Lexical Environment 被創造的時候就已經被宣告，變數是在執行到時才被創造。這是為什麼可以在函式宣告前使用。*

![](/files/-LhdtLcYMg6zbfWATdSI)

### Inner and outer Lexical Environment

![](/files/-Lhdv-hq9uHVHTcdmiic)

當使用函式時，會創造 2 個 Lexical Environment， inner Lexical Environment 的屬性有帶入該函式的參數，outer Lexical Environment 有全域變數包含函式本身及用到的全域變數。當函式使用變數時，會先在內部尋找，然後尋找外部，然後是全域變數。在 "use strict" 下，變數未定億會出現錯誤，非 "use strict"，會指定變數值為 undefined。

![](/files/-Lhdw52UH0rdFTyF8gjf)

```javascript
// 回答第 1 個問題，變數會使用最新的值，
let name = "John";

function sayHi() {
  alert("Hi, " + name);
}

name = "Pete"; // (*)

sayHi(); // Pete
```

## Nested functions

```javascript
function sayHiBye(firstName, lastName) {

  // helper nested function to use below
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );

}

// 巢狀函式可以被返回作為新物件的屬性或直接作為結果。

// 巢狀函式作為新物件屬性
// constructor function returns a new object
function User(name) {

  // the object method is created as a nested function
  this.sayHi = function() {
    alert(name);
  };
}

let user = new User("John");
user.sayHi(); // the method "sayHi" code has access to the outer "name"

// 巢狀函式作為結果
function makeCounter() {
  let count = 0;

  return function() {
    return count++; // has access to the outer "count"
  };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
```

![](/files/-Lhe-MsiiXn5ooLQkTWM)

```javascript
// 可以在 makeCounter() 外改變裡面的變數 count 嗎?
// 不行，count 是區域變數，無法從外部得到他的值
// 當執行 makeCounter() 多次，每次執行結果使獨立的嗎? count 相同嗎?
// 每次執行 makeCounter() 會產生新的 Lexical Environment，所以每次執行的結果互為獨立。

function makeCounter() {
  let count = 0;
  return function() {
    return count++;
  };
}

let counter1 = makeCounter();
let counter2 = makeCounter();

alert( counter1() ); // 0
alert( counter1() ); // 1

alert( counter2() ); // 0 (independent)
```

## Environments in detail

1\. 腳本開始時只有全域 Lexical Environment，裡面只有宣告 makeCounter()，所有函式宣告的時候，必然有一個隱藏的屬性 \[\[Environment]] 指向函式，

![](/files/-LheNfqauuZrccdhpLt3)

2\. 呼叫函式的時候，創造 Lexical Environment，Environment Record 儲存區域變數，指向外部 lexical reference 被設定成 \[\[Environment]] 屬性的函式。

![](/files/-LhePEIH37-j5__46CvK)

3\. 執行到巢狀函式時，創造 \[\[Environment]] 屬性外部指向 makeCounter()。

![](/files/-LheQ2NVC87olepD_iqf)

4\. makeCounter() 執行完畢，結果儲存在全域變數 counter，當 counter 被呼叫執行 return count++ 這行程式碼。

![](/files/-LheQsWwIEBhQ2erPOm6)

5\. 雖然 makeCounter() 執行完畢，但仍有 \[\[Environment]] 屬性指向他，因此呼叫 counter() 時，會先創造一個空的 Lexical Environment 因為沒有參數，再來指向外部的 \[\[Environment]] 。

![](/files/-LheRqb5TK94wGYrDRF8)

6\. 呼叫 counter()，不只返回 count 值，也增加他的值。

![](/files/-LheSEhmdnvnvzO9Iepw)

開頭問題的解答：

![](/files/-LheSTvVpEa83FOKK5Z4)

## Code blocks and loops, IIFE

閉包的特性可以應用在任何 {...} 上。

### if

![](/files/-LheU3mnjLQI0DvS_MdW)

### for

```javascript
for (let i = 0; i < 10; i++) {
  // Each loop has its own Lexical Environment
  // {i: value}
}

alert(i); // Error, no such variable
```

### Code blocks

```javascript
// 當 2 個腳本有相同痊癒變數會出現問題，用 {...} 可以解決這樣的問題。
{
  // do some job with local variables that should not be seen outside

  let message = "Hello";

  alert(message); // Hello
}

alert(message); // Error: message is not defined
```

### IIFE

```javascript
// 在過去沒有 lexical environment，因此發明 immediately-invoked function expressions，
// 函式有區域變數並立即執行，

(function() {

  let message = "Hello";

  alert(message); // Hello

})();

// 沒有函式名稱
// Try to declare and immediately call a function
function() { // <-- Error: Unexpected token (

  let message = "Hello";

  alert(message); // Hello

}();

// 不能宣告同時執行
// syntax error because of parentheses below
function go() {

}(); // <-- can't call Function Declaration immediately

// Ways to create IIFE

(function() {
  alert("Parentheses around the function");
})();

(function() {
  alert("Parentheses around the whole thing");
}());

!function() {
  alert("Bitwise NOT operator starts the expression");
}();

+function() {
  alert("Unary plus starts the expression");
}();
```

## Garbage collection

```javascript
// 函式執行完，會被記憶體清除
function f() {
  let value1 = 123;
  let value2 = 456;
}

f();

// 用閉包的特性，函式執行完仍在記憶體內，[[Environment] 指向函式。
function f() {
  let value = 123;

  function g() { alert(value); }

  return g;
}

let g = f(); // g is reachable, and keeps the outer lexical environment in memory

// 函式被呼叫多次，相對應的 lexical environment，也會被儲存在記憶體。
function f() {
  let value = Math.random();

  return function() { alert(value); };
}

// 3 functions in array, every one of them links to Lexical Environment (LE for short)
// from the corresponding f() run
//         LE   LE   LE
let arr = [f(), f(), f()];

// 當全域變數被清除，函式消失
function f() {
  let value = 123;

  function g() { alert(value); }

  return g;
}

let g = f(); // while g is alive
// there corresponding Lexical Environment lives

g = null; // ...and now the memory is cleaned up
```

### Real-life optimizations

```javascript
// 實際狀況 JavaScript 引擎會進行優化，未作用的變數會被清除。
// v8 在 debug 時無法使用區域變數
function f() {
  let value = Math.random();

  function g() {
    debugger; // in console: type alert( value ); No such variable!
  }

  return g;
}

let g = f();
g();

// 會返回全域變數
let value = "Surprise!";

function f() {
  let value = "the closest value";

  function g() {
    debugger; // in console: type alert( value ); Surprise!
  }

  return g;
}

let g = f();
g();
```
