一個例子講清楚什麼是閉包,什麼是記憶體銷燬
//前言
閉包,這個概念對於每位JSer而言都不陌生,它幾乎伴隨著每個前端入門者的初學階段,重要到幾乎每家公司面試都會問。
關於閉包究竟是什麼,閉包乾嘛用的,網上各種回答也是五花八門,動不動就扯到隱匿變數/記憶體洩漏這些概念,讓沒有C基礎的初學者越看越暈,我不能說那些是錯的,不過顯然對新手不太友好。曾幾何時我也是被那些個故作高深的概念繞得七葷八素雲裡霧裡,那今天這篇文章以一個簡單到80歲老太都看得懂的demo,來闡明閉包的本質及作用
//變數(記憶體)銷燬
看過紅寶書的都見過這個詞,不過Nicholas大神的例子我覺得還是有點晦澀,我來舉個更簡易的例子——現在有一個button和一個有數字的span,button負責讓數字加1,程式碼如下:
#例子1
html <body> <button id="add">加1</button> <span id="span">10</span> </body> js <script> var a = 10; add.onclick = function (){ a++; span.innerHTML = a; } </script>
簡單到不能再簡單了吧,我每次按add按鈕,就會讓數字加一,現在把js部分做個改變:
#例子2
<script> add.onclick = function (){ var a = 10; a++; span.innerHTML = a; } </script>
聰明的你想必已經看出來了,將宣告變數a這個步驟從外面挪到onclick事件函式裡面的話,不管你怎麼按a將永遠保持11,頁面的span裡的數字也將永遠是11,為什麼?你可能會說:因為每次按按鈕的時候,我都重新聲明瞭一個變數a啊,等於每次都重置為10了啊,每次++只能是11然後無限迴圈balabala。。。好,那我問你第二個問題:為什麼宣告a這一步寫在函式外面就行了?你會說因為a一開始只聲明瞭一次啊,每次++好的a都是在舊的a上面進行操作啊balabala。。。這個回答不能說錯,但顯然沒有進行深入的思考,就像古時候人們都覺得蘋果落地是天經地義的一樣,只有保持懷疑,才有機會推匯出萬有引力定律。
紅寶書告訴我們,沒有用的區域性變數就會被銷燬記憶體,一開始我百思不得其解(沒辦法,沒有C基礎嘛),後來通過這個點選的例子就鬧明白了——在你每次按好按鈕,函式執行完畢後,你的這個a就被當成沒有用的變數被回收了,或者換句話說,記憶體被銷燬了!所以你的a始終都是初始的那個10!而在例子1的js程式碼裡,a是全域性變數,全域性變數只有當你關閉頁面or瀏覽器的時候才會被銷燬!所以a彷彿就有了儲存功能,能夠記錄每次變化後的值,你點或不點,a就在那乖乖的,保持著上一次點選好++的那個值。
好,我相信你看到這裡應該明白回收變數(這個詞在js裡其實就等價於銷燬記憶體)是什麼個意思了,那這和我們今天主題“閉包”有什麼關係?別急,看程式碼,我給你在例子1上做點小改動,你就看明白了!
#例子3
<script> (function (){ var a = 10; add.onclick = function (){ a++; span.innerHTML = a; } })() </script>
我在例子1的外面加了自執行匿名函式包裹了一下,現在你就可以把這段操作看成是函式包裹函式的形式了,放心,程式碼一樣能跑!現在我們把外面的匿名函式成為father函式,把裡面點選按鈕的函式稱為child函式,沒毛病吧!畢竟包裹關係,好,現在我就可以說,這就是個閉包環境了,為什麼?因為child函式引用了father函式的變數a啊!閉包的最大特性就是,如果裡函式引用(or訪問,這倆詞在這個語境下是等價的)了外函式的某個變數,那這個變數就能享受和全域性變數一樣的特權,丫不會被回收!除非你關閉頁面or瀏覽器!這也就是為什麼a能一直++並正常顯示的問題了,因為他被child函式一直訪問著,不會被銷燬!不會被銷燬!不會被銷燬!重要的事情說三遍!閉包就這個作用!
好的,那順著思路繼續走,那記憶體洩漏是什麼鬼?很簡單,如果你像這種享受全域性變數不會被銷燬的特權的閉包變數多到一定數量了,那記憶體就要撐爆了,畢竟咱的電腦對待瀏覽器是很摳門的,記憶體就分配給你那麼點,一多就會爆,這就是記憶體洩漏,並不是什麼高大上的概念!
//結束語
相信看到這裡你應該能夠大致明白閉包,以及記憶體銷燬的大致概念了吧~其實我對外面那些講解閉包的文章最不滿意的一點就是,各種外函式return裡函式,這種寫法會給新手們的理解制造很大的障礙,畢竟return來return去,要呼叫的時候還要再加一對(),實在反人類,並且最大的問題在於:看多了return的例子,新手們很可能會誤認為只有外函式return裡函式才是閉包!壓根不是這樣的!閉包跟return是既不充分也不必要的關係!(雖然實際業務中我們經常要return個裡函式來達到強行閉包的效果),並且,閉包和自執行匿名函式也是既不充分也不必要的關係,也不要因為我這個例子就有這種想法,return裡函式也好,自執行也好,都只是為了方便理解而已,你甚至可以把全域性作用域當成一個最外層的自執行匿名函式的區域性環境,就像例子3這樣,或者在所有程式碼外面都包裹上window.onload = function (){}這樣看的話,所有引用到最外層變數的函式,就都是閉包了!感謝閱讀!
---------------------
原文:https://blog.csdn.net/coder_vader/article/details/78839686