# 閉包
**詞法作用域(lexical scope)=靜態作用域**
JavaScript 的作用域範圍由程式碼所定,所以程式碼在撰寫完成的同時,就已經先確立了作用域,運行的過程中都不會改變其作用域
```
function fn1() {
console.log(a);
}
function fn2() {
var a = 1;
fn1();
}
fn2();
```
兩個函式的作用域完全獨立(a is not defined)

變數的作用域是獨立的,但函式內的函式則可以取用外層作用域的變數=>**作用域鏈(scope chain)**:子物件會一級一級地向上尋找所有父物件的變數
```
function sayHi() {
var name = '小明';
function addString() { // 內部函式、閉包
console.log(`${name} 你好`); // 取用外層的變數
}
addString();
}
sayHi();
```

在巢狀函式中,如果內層的函式沒有可以取用的特定變數則會向外查找,此時內部的函式就可以稱為閉包。閉包是內部的函式可以取用外部作用域變數/參數的概念
addString可以讀取sayHi中的局部變數=>把addString作為返回值=>外部讀取內部變數
閉包發生的時機是在函式建立的時候,每當新的函式被建立出來,**它會紀錄它所在的位置的*執行環境*,並記錄外層的*作用域鏈*。**
**執行環境(Execution Context,EC)**
js底層在程式準備執行時,針對「全域」及「函式」所建立的物件
1. 全域執行環境(Global Execution Context):
創造階段(creation phase)------------------------->執行階段(execution phase)
* 建立全域物件(Global Object)--------------------一行一行(line by line)執行code
* 建立 this,並將它指向window
* 建立scope chain,並設為 null(因為他自己就是最頂層)
* hoisting
2. 函式執行環境(Function Execution Context)
創造階段(creation phase)------------------------->執行階段(execution phase)
* 建立執行物件(Activation Object)----------------一行一行(line by line)執行code
* 建立 this,並把它指向呼叫此函式的 caller
* 建立Scope chain並把它指向此函式的外層
* hoisting
```
var a = 0
function b() {
var a = 10
function c() {
console.log(a)
}
return c
}
var func = b() //變數 func =return的函式c
func() // 10
```

js底層運作流程:
1. 建立 Global EC:預留了變數 a、func 的空間,及函式 b 的作用域鏈
2. 執行 Global EC:變數 a 賦值 a = 0,呼叫函式 b()
3. 準備函式 b:建立 function b() EC,預留了區域變數 a 的空間,及函式 c 的作用域鏈
4. 執行 b():區域變數 a 賦值 a = 10,回傳函式 c
5. 函式 b 執行結束:消除 function b() EC
=>function b() EC 內的 a 被回傳的函數 c 閉包了
6. 變數 func 賦值成函式 b() 的執行結果 - return的函式 c
7. 呼叫 func():準備函式 c,建立 function c() EC,並依照作用域鏈找到 function b() EC 中的區域變數 a
8. 執行 func():執行 console.log(a);印出 a=10
9. func() 執行結束,消除 function c()的EC,程式執行結束
由於閉包,在 b() 執行結束時,其中的區域變數 a 並未跟著 function b() EC 一起消失,而是留給了 function c() EC 的參照使用,變數a因為還會持續維持參考,所以不會被釋放記憶體,因此成為了 func() 函式的**私有變數**(僅存與此函式中,無法透過其它方式調整)
**私有變數**
任何在函式中定義的變數,不能在函式外部訪問的變數,都可以認為是私有變數。包括函式的參數,局部變數和在函式內部定義的其他函式
私有變數=>優點:隱藏那些不應該被直接修改的資料
EX:一個簡易的後台系統
```
var commodities = [
{ name: "iphone", stock: 10 },
{ name: "htc", stock: 5 },
];
function add(name, number) {
commodities.push({ name: name, stock: number });
}
function changeStock(id, amount) {
commodities[id].stock += amount;
}
add("乖乖", 20);
changeStock(0, -5);
commodities[0].stock += 10;
```
問題?
全部商品的資料暴露在外,有心人士能夠直接操作裡面的資料。
解決?=>引入閉包
```
function createstore() {
var commodities = [
{ name: "iphone", stock: 10 },
{ name: "htc", stock: 5 },
];
return {
all: function () {
return commodities;
},
add: function (name, number) {
commodities.push({ name: name, stock: number });
},
changeStock: function (id, amount) {
commodities[id].stock += amount;
},
};
}
var myStore = createstore();
myStore.add("乖乖", 20);
console.log(myStore.all());
myStore.changeStock(0, -5);
console.log(myStore.all());
commodities[0].stock += 10; //reference error
```
上面的函式回傳一個物件,裡面包含的三個函式。因為閉包的關係,所以commodities這個陣列會被鎖在函式中,而且持續可被也僅可被後面三個函式(**特權方法**)所存取,但外界是看不到這個變數的,因為已經是作用域以外的世界。
**特權方法**︰有權訪問私有變數的公有方法
疑點?
外加個函式屬性給myStore,是不是也能存取到commodities?
```
'use strict'
...
myStore.removeAll = function () {
commodities = [];
};
myStore.removeAll(); //Uncaught ReferenceError
```
就算外加屬性給已經包裝好的物件,也沒辦法存取到原先的閉包,因為閉包一旦創建完成,就具有不可侵犯性。
**結論:**
Q1解釋何謂閉包?
1. 所有函式在建立時都會產生閉包。
2. 閉包不是只會產生在巢狀(內部)函式的回傳時,巢狀(內部)函式是一種最常利用閉包結構的樣式,因為它可以重覆使用閉包中的記憶環境。
3. 閉包所記憶的環境,其原理是來自作用域連鎖的設計,內部函式可以看(獲取)到外部函式的變數值與傳入參數值。
4. 閉包中的變數是私有的,只有閉包函式才有許可權訪問它。不會被外面的變數和方法給汙染。
Q2為什麼要使用閉包?
如果你希望「別人要依據你規範好的方法操作狀態」:
透過應用閉包私有變數的特性,外部僅能通過指定的特權方法訪問該變數,防止變數汙染,確保資料安全性與穩定性。
**題目:**
```
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
},
};
alert(object.getNameFunc()());
```
```
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
},
};
alert(object.getNameFunc()());
```
-----
參考資料:
[JS 核心觀念筆記 - 閉包基本認識、工廠模式與私有方法](https://hsuchihting.github.io/javascript/20200812/3642687092/)
[閉包,原來這就是閉包啊!](https://ithelp.ithome.com.tw/articles/10244348)
[閉包包什麼?探索JS中的作用域與Closure](https://medium.com/%E7%8B%97%E5%A5%B4%E5%B7%A5%E7%A8%8B%E5%B8%AB/%E9%96%89%E5%8C%85%E5%8C%85%E4%BB%80%E9%BA%BC-%E6%8E%A2%E7%B4%A2js%E4%B8%AD%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F%E8%88%87closure-javascript%E9%8D%9B%E9%8D%8A%E6%97%A5%E8%A8%98-f7b1a2ac1e2a)
[題目的解釋連結!](https://www.imooc.com/wenda/detail/352112)