# 閉包 **詞法作用域(lexical scope)=靜態作用域** JavaScript 的作用域範圍由程式碼所定,所以程式碼在撰寫完成的同時,就已經先確立了作用域,運行的過程中都不會改變其作用域 ``` function fn1() { console.log(a); } function fn2() { var a = 1; fn1(); } fn2(); ``` 兩個函式的作用域完全獨立(a is not defined) ![](https://i.imgur.com/RbHBA5H.png) 變數的作用域是獨立的,但函式內的函式則可以取用外層作用域的變數=>**作用域鏈(scope chain)**:子物件會一級一級地向上尋找所有父物件的變數 ``` function sayHi() { var name = '小明'; function addString() { // 內部函式、閉包 console.log(`${name} 你好`); // 取用外層的變數 } addString(); } sayHi(); ``` ![](https://i.imgur.com/QxinGUh.png) 在巢狀函式中,如果內層的函式沒有可以取用的特定變數則會向外查找,此時內部的函式就可以稱為閉包。閉包是內部的函式可以取用外部作用域變數/參數的概念 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 ``` ![](https://i.imgur.com/A18OFeV.png) 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)