# 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();
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mistborn.gitbook.io/til-coding/javascript/closure.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
