iT邦幫忙

1

克服 JS 的奇怪部分:閉包、外部環境參照、範圍鏈

這些觀念從"克服 JS 的奇怪部分"影片中學習的,這系列影片真的是好東西,所謂的閉包(closure),也就是說執行環境可以將那些它所要reference的外部變數給關住、包住,即便執行環境已經消失,此現象稱作閉包(closure),在介紹閉包之前我們先來解釋一下外部環境參照(Reference to Outer Environment)、範圍鏈(scope chain)的東西。

外部環境參照

每個執行環境都有一個參照到他的外部環境,所謂的外部環境參照(Reference to Outer Environment)就是指說當使用變數時,js不只會在當前的執行環境中尋找變數,也會到外部環境參照中尋找變數,而js會依照物理位置來判斷其外部參照環境為何

此處函數b及a之物理位置皆在全域環境底下,如下列例子,首先我們全域執行環境中有func b,func a,let myVar,呼叫a函數以後,創建函數a的執行環境,並且他的變數環境有myVar=2,接著,呼叫b函數,其執行環境消失,離開執行堆,但是變數myVar=2並不會消失,而是在記憶體空間保留,再來就創建函數b的執行環境,做console操作然後執行環境消失,離開執行堆,所以b的外部參照環境就是我們的全域執行環境,而a外部參照環境也是一樣。

        let myVar = 1;

        function b() {
            console.log(myVar);
        }

        function a() {
            let myVar = 2;
            b();
        }
        a(); // 1

https://ithelp.ithome.com.tw/upload/images/20200806/20126182fZ5pfWP233.png

但當我們將在函數a中所宣告的變數myVar給取消,也就是把let拿掉,那我們的變數myVar就會變成全域變數,此時結果就會變成2,儘管我們將在全域環境所設置的myVar放置在函數a後面,結果也會是一樣,我們在a所宣告的全域變數myVar就會將原本的myVar所覆蓋

       let myVar = 1;
        function b() {
            console.log(myVar);
        }

        function a() {
            myVar = 2;
            b();
        }
        // let myVar = 1;
        a(); // 2

範圍鏈

再來我們解釋一下範圍鏈,範圍指的就是我所能取到變數的地方,而鏈是指我外部環境參照的連結,所謂範圍鏈指的就是我們需要某個執行環境內程式碼的變數,如果他在的當前環境下找不到變數,會往外部環境找,在執行堆中由上往下找,直到全域環境

而下列例子,我們將函數b包在函數a中,也就是b的物理位置在函數a中,所以當我們呼叫函數a,就會創建函數b,並且變數環境會創建一個myVar=2(會保留在記憶體空間),而後離開執行堆,並呼叫函數b,而由於函數b之物理位置位於a之中,因此他會認定函數b之外部參照環境為函數a,而就會獲取到未消失的myVar=2,而a的外部參照環境仍為全域環境。

        let myVar = 1;

        function a() {

            function b() {
                console.log(myVar); // 2
            }
            let myVar = 2;
            b();
        }
        a();

https://ithelp.ithome.com.tw/upload/images/20200806/20126182NM7wLsaobq.png

閉包

介紹完上述兩個觀念後,我們直接來介紹閉包並且使用例子,首先我們在全域環境會有兩個變數分別為greetEnglish、
greetSpanish,還有一個func makeGreeting,接著逐行執行到greetEnglish呼叫makeGreeting函數,創造其執行環境,並創造變數環境將language="en"給設置進去,而後回傳匿名函數,存入greetEnglish指向其函數,然後離開執行堆,記住我們執行環境的記憶體空間仍在(en不會消失),再來greetSpanish也是同樣的步驟。

https://ithelp.ithome.com.tw/upload/images/20200807/20126182sPjzxAqQeC.png

此時,我有兩個不同執行環境的記憶體位置,而後,我們呼叫greetEnglish,也就是呼叫我們所回傳的函數,而後我們在創建一個匿名函數的執行環境並創建其變數環境(firstname="John", lastname="Doe"),而此時外部參考環境會指向一個執行環境,js會知道我們第一個所創建的執行環境並指向它,也就是會獲取到"en",而框起來的範圍就是我們的閉包,而greetSpanish的步驟一樣,再次強調,每當呼叫一個函數,就會創建他的執行環境,而在裡面被創建的函數,就會指向那個執行環境、指向其記憶體空間

        function makeGreeting(language) {
            return function (firstname, lastname) {
                if (language == "en") {
                    console.log("hi" + firstname + "" + lastname);
                }
                if (language == "es") {
                    console.log("Hola" + firstname + "" + lastname);
                }
            }
        }
        let greetEnglish = makeGreeting("en");
        let greetSpanish = makeGreeting("es");

        greetEnglish("John", "Doe");
        greetSpanish("John", "Doe");

https://ithelp.ithome.com.tw/upload/images/20200807/2012618258x9Q7syA1.png


1 則留言

1
fillano
iT邦超人 1 級 ‧ 2020-08-07 10:44:42

記得應該是IE3,有一個bug...

因為他的Javascript引擎是一個作業系統共用的獨立元件,結果使用閉包以後,Javascript引擎使用的記憶體就無法回收,造成嚴重的記憶體洩漏。

當時微軟的建議是:不要使用閉包/images/emoticon/emoticon04.gif

no027843 iT邦新手 5 級 ‧ 2020-08-12 17:44:05 檢舉

謝謝大大回覆,IE真ㄉ很讓人頭疼R ~~

我要留言

立即登入留言