【js基礎修煉之路】— 深刻淺出理解閉包

以前對於閉包的理解只是很膚淺的,只是浮於表面,此次深究了一下閉包,下面是我對閉包的理解。javascript

什麼是閉包?

引用高程裏的話 =>
閉包就是有權訪問另外一個做用域中變量的函數,閉包是由函數以及建立該函數的詞法環境組成而成
記住,閉包是一個函數,廢話很少說,先來個例子:html

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

函數makeFunc裏面定義了一個函數,該函數引用了一個makeFunc內部的變量,而後返回該函數,myFunc變量承接了makeFunc返回的函數,也就是
function displayName() { alert(name); }java

在下面執行該函數,咦!!!爲何函數裏面的name是從哪裏獲得的,爲何不報錯呢?
要搞明白其中的細節,必須從理解函數被調用的時候都會發生什麼入手node

那麼什麼是做用域鏈呢?

當一個函數被調用的時候,會建立一個執行環境(執行上下文),以及相應的做用域,在函數執行完的時候該做用域鏈會被銷燬,函數裏面的變量也會被回收,可是閉包的狀況不是這樣的..
image.png
仍是上面的例子,打印了一下myFunc函數,發現該函數的做用域鏈(scopes)保存着name變量,name變量並無被回收,因而咱們成功的在makeFunc函數外的做用域取到了name變量,myFunc就是閉包,咱們重溫一下閉包的特色=>閉包是由函數以及建立該函數的詞法環境組成而成.
這下概念就理解的更清楚了吧!閉包

閉包與變量

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <p id="help">Helpful notes will appear here</p>
    <p>E-mail: <input type="text" id="email" name="email"></p>
    <p>Name: <input type="text" id="name" name="name"></p>
    <p>Age: <input type="text" id="age" name="age"></p>
    <script type="text/javascript">
    function showHelp(help) {
        document.getElementById('help').innerHTML = help;
    }
    var helpText = [
        { 'id': 'email', 'help': 'Your e-mail address' },
        { 'id': 'name', 'help': 'Your full name' },
        { 'id': 'age', 'help': 'Your age (you must be over 16)' }
    ];

    function returnHelp(i) {
        return helpText[i];
    }

    function setupHelp() {
        for (var i = 0; i < helpText.length; i++) {
            var item = returnHelp(i);
            document.getElementById(item.id).onfocus = function() {
                showHelp(item.help);
            }
        }
    }
    setupHelp(); 
    </script>
</body>

</html>

原本想實現一個點擊輸入框,顯示提示信息的功能,可是結果是顯示的全是age信息。。。爲何???app

緣由是賦值給onfouse的是閉包,這些閉包是由他們的函數定義和在setupHelp做用域中捕獲的環境所組成的 這三個閉包在循環中被建立,但他們共享同一個詞法做用域,在這個做用域中存在一個變量item, 點擊輸入框的時候,item已經變成helpText 的最後一個了,因此。。。
怎麼解決呢?
既然聚焦輸入框後的回調函數裏面的item變量引用的是共享的,因此只要把他變成非共享的就能夠了,異步

function showHelp(help) {
        document.getElementById('help').innerHTML = help;
    }
    var helpText = [
        { 'id': 'email', 'help': 'Your e-mail address' },
        { 'id': 'name', 'help': 'Your full name' },
        { 'id': 'age', 'help': 'Your age (you must be over 16)' }
    ];

    function returnHelp(i) {
        return helpText[i];
    }

    function setupHelp() {
        for (var i = 0; i < helpText.length; i++) {
            var item = returnHelp(i);
            //經過這種方法把每個item變成具體的實例,而不是變量,避免每一次聚焦的時候執行函數裏面的變量都是指向同一個數
            document.getElementById(item.id).onfocus = change(item);
        }
    }

    function change(item) {
        return function() {
            showHelp(item.help);
        }
    }
    setupHelp();

這樣把每一次循環的item傳給外面的函數,而後外邊函數返回具體的實例而非變量,這樣就解決了問題。函數

下面再來看一個經典的例子 :code

<html>
    <body>
        <div>1</div>
        <div>2</div>
        <div>3</div>
        <div>4</div>
        <div>5</div>
    <script>
        var nodes = document.getElementsByTagName('div');
        for(var i = 0, len = nodes.length; i < len; i++){
            nodes[i].onclick = function(){
                alert(i);
            }
        };
    </script>
  </body>
</html

若是你認爲點擊每個div會返回對應的索引,那麼恭喜你,你掉坑了!!!
沒錯,一開始我也掉坑了,可能你們會感到很疑惑,爲何點徹底是5,由於點擊事件是異步的,點擊的時候變量 i 已經變成5了,因此點擊的時候彈出的全是5,怎麼解決呢,閉包來了htm

for(var i = 0, len = nodes.length; i < len; i++){
    (function(i){       
        nodes[i].onclick = function(){
                console.log(i);
            }
        })(i)
};

這段代碼是如何完美的解決問題的呢?
當咱們點擊的時候,回調函數裏面的 i 引用的是外面閉包的 i ,這樣問題就被完美的解決了。

閉包的缺點

在上面分析的過程當中也說到過,閉包不會被垃圾回收機制回收,會形成內存泄漏,記得閉包使用完手動把變量回收一下。

相關文章
相關標籤/搜索