【js基礎(chǔ)修煉之路】— 深入淺出理解閉包

之前對(duì)于閉包的理解只是很膚淺的,只是浮于表面,這次深究了一下閉包,下面是我對(duì)閉包的理解。

什么是閉包?

引用高程里的話 =>
閉包就是有權(quán)訪問另一個(gè)作用域中變量的函數(shù),閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組成而成
記住,閉包是一個(gè)函數(shù),廢話不多說,先來個(gè)例子:

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

var myFunc = makeFunc();
myFunc();

函數(shù)makeFunc里面定義了一個(gè)函數(shù),該函數(shù)引用了一個(gè)makeFunc內(nèi)部的變量,然后返回該函數(shù),myFunc變量承接了makeFunc返回的函數(shù),也就是
function displayName() { alert(name); }

在下面執(zhí)行該函數(shù),咦?。?!為什么函數(shù)里面的name是從哪里得到的,為什么不報(bào)錯(cuò)呢?
要搞明白其中的細(xì)節(jié),必須從理解函數(shù)被調(diào)用的時(shí)候都會(huì)發(fā)生什么入手

那么什么是作用域鏈呢?

當(dāng)一個(gè)函數(shù)被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境(執(zhí)行上下文),以及相應(yīng)的作用域,在函數(shù)執(zhí)行完的時(shí)候該作用域鏈會(huì)被銷毀,函數(shù)里面的變量也會(huì)被回收,但是閉包的情況不是這樣的..


image.png

還是上面的例子,打印了一下myFunc函數(shù),發(fā)現(xiàn)該函數(shù)的作用域鏈(scopes)保存著name變量,name變量并沒有被回收,于是我們成功的在makeFunc函數(shù)外的作用域取到了name變量,myFunc就是閉包,我們重溫一下閉包的特點(diǎn)=>閉包是由函數(shù)以及創(chuàng)建該函數(shù)的詞法環(huán)境組成而成.
這下概念就理解的更清楚了吧!

閉包與變量

<!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>

本來想實(shí)現(xiàn)一個(gè)點(diǎn)擊輸入框,顯示提示信息的功能,但是結(jié)果是顯示的全是age信息。。。為什么???

原因是賦值給onfouse的是閉包,這些閉包是由他們的函數(shù)定義和在setupHelp作用域中捕獲的環(huán)境所組成的 這三個(gè)閉包在循環(huán)中被創(chuàng)建,但他們共享同一個(gè)詞法作用域,在這個(gè)作用域中存在一個(gè)變量item, 點(diǎn)擊輸入框的時(shí)候,item已經(jīng)變成helpText 的最后一個(gè)了,所以。。。
怎么解決呢?
既然聚焦輸入框后的回調(diào)函數(shù)里面的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);
            //通過這種方法把每一個(gè)item變成具體的實(shí)例,而不是變量,避免每一次聚焦的時(shí)候執(zhí)行函數(shù)里面的變量都是指向同一個(gè)數(shù)
            document.getElementById(item.id).onfocus = change(item);
        }
    }

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

這樣把每一次循環(huán)的item傳給外面的函數(shù),然后外邊函數(shù)返回具體的實(shí)例而非變量,這樣就解決了問題。

下面再來看一個(gè)經(jīng)典的例子 :

<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

如果你認(rèn)為點(diǎn)擊每一個(gè)div會(huì)返回對(duì)應(yīng)的索引,那么恭喜你,你掉坑了?。?!
沒錯(cuò),一開始我也掉坑了,可能大家會(huì)感到很疑惑,為什么點(diǎn)完全是5,因?yàn)辄c(diǎn)擊事件是異步的,點(diǎn)擊的時(shí)候變量 i 已經(jīng)變成5了,所以點(diǎn)擊的時(shí)候彈出的全是5,怎么解決呢,閉包來了

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

這段代碼是如何完美的解決問題的呢?
當(dāng)我們點(diǎn)擊的時(shí)候,回調(diào)函數(shù)里面的 i 引用的是外面閉包的 i ,這樣問題就被完美的解決了。

閉包的缺點(diǎn)

在上面分析的過程中也說到過,閉包不會(huì)被垃圾回收機(jī)制回收,會(huì)造成內(nèi)存泄漏,記得閉包使用完手動(dòng)把變量回收一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • $ 閉包是什么? ? 閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合,是指有權(quán)訪問另一個(gè)函數(shù)作用域中變量的函數(shù)??赡苈犃藭?huì)...
    果汁涼茶丶閱讀 888評(píng)論 0 3
  • 談起閉包,它可是JavaScript兩個(gè)核心技術(shù)之一(異步和閉包),在面試以及實(shí)際應(yīng)用當(dāng)中,我們都離不開它們,甚至...
    sponing閱讀 775評(píng)論 0 7
  • 閉包 在本文章中** 閉包是指那些能夠訪問獨(dú)立(自由)變量的函數(shù) (變量在本地使用,但定義在一個(gè)封閉的作用域中)。...
    __Seve閱讀 631評(píng)論 0 0
  • 也不知道寫什么,反倒是想寫卻寫不出。我愛文字,但現(xiàn)在卻沒有這樣的能力寫出表達(dá)心情的文字。
    香奈兒緣閱讀 423評(píng)論 0 0
  • 學(xué)期:第377期 隊(duì)呼:感謝二組 姓名:沈一寧 公司: 揚(yáng)州日發(fā)干燥設(shè)備工程有限公司 【日精打卡第31天】 【...
    寧_79e3閱讀 127評(píng)論 0 0

友情鏈接更多精彩內(nèi)容