標籤:img 並且 頁面 標記 inf 效能 記憶體 函數 src
相信很多人都有看過關於閉包的文章,但是真正意義上的瞭解清楚的也不多,今天我們就來談談對閉包的理解。
閉包在JavaScript中一直是一個很重要的存在,閉包很重要但是又很難理解,起初我也是這樣認為,但只要真的清楚之後,你會覺得很有趣。
我們先來看一個閉包的例子:
1 function foo() {2 let a = 2;3 function bar() {4 console.log(a);5 }6 return bar;7 }8 let baz = foo();9 baz();
大家肯定都寫過類似的代碼,相信很多小夥伴也知道這段代碼應用了閉包,但是,為什麼會產生閉包,閉包又是在哪裡?
回答上面的問題,首先必須Crowdsourced Security Testing道閉包是什麼,才能分析出閉包為什麼產生和閉包到底在哪?
當一個函數能夠記住並訪問到其所在的詞法範圍及範圍鏈,特彆強調是在其定義的範圍外進行的訪問,此時該函數和其上層執行內容共同構成閉包。
需要明確幾點:
1、閉包一定是函數對象
2、閉包和詞法範圍,範圍鏈,記憶體回收機制息息相關
3、當函數一定是在其定義的範圍外進行的訪問時,才產生閉包
4、閉包是由該函數和其上層執行內容共同構成
閉包是什麼,我們說清楚了,下面我們看下閉包是如何產生的。
現在我假設JS引擎執行到這行代碼:
let baz = foo();
此時,JS的範圍是這樣的:
這個時候foo函數已經執行完,JS的記憶體回收機制應該會自動將其標記為"離開環境",等待回收機制下次執行,將其記憶體進行釋放(標記清除)。
但是,我們仔細看圖中粉色的箭頭,我們將bar的引用指向baz,正是這種引用賦值,阻止了記憶體回收機制將foo進行回收,從而導致bar的整條範圍鏈都被儲存下來。
接下來,baz()執行,bar進入執行棧,閉包(foo)形成,此時bar中依舊可以訪問到其父範圍氣泡中的變數a。
這樣說可能不是很清晰,接下來我們藉助chrome的調試工具看下閉包產生的過程。
當JS引擎執行到這行代碼let baz = foo();時:
圖中所示,let baz = foo();已經執行完,即將執行baz();,此時Call Stack中只有全域上下文。
接下來baz();執行:
我們可以看到,此時bar進入Call Stack中,並且Closure(foo)形成。
針對上面我提到的幾點進行下說明:
1、上述第二點(閉包和詞法範圍,範圍鏈,記憶體回收機制息息相關)大家應該都清楚了
2、上述第三點,當函數baz執行時,閉包才產生
3、上述第四點,閉包是foo,並不是bar,很多書(《you dont know JavaScript》《JavaScript進階程式設計》)中,都強調儲存下來的引用,即上例中的bar是閉包,而chrome認為被儲存下來的封閉空間foo是閉包
仔細想來,在我們範圍模型中,範圍鏈讓我們的內部bar氣泡能夠"看到"外面的世界,而閉包則讓我們的外部範圍能夠"關注到"內部的情況成為可能。可見,只要我們願意,內心世界和外面世界是可以相通的。
使用閉包時的注意事項:
閉包,在JS中絕對是一個高貴的存在,它讓很多不可能實現的代碼成為可能,但是物雖好,也要合理使用,不然不但不能達到我們想要的效果,有的時候可能還會適得其反。
記憶體流失(Memory Leak)
JavaScript分配給Web瀏覽器的可用記憶體數量通常比分配給傳統型應用程式的少,這樣做主要是防止JavaScript的網頁耗盡全部系統記憶體而導致系統崩潰。
因此,要想使頁面具有更好的效能,就必須確保頁面佔用最少的記憶體資源,也就是說,我們應該保證執行代碼只儲存有用的資料,一旦資料不再有用,我們就應該讓記憶體回收機制對其進行回收,釋放記憶體。
我們現在都知道了閉包阻止了記憶體回收機制對變數進行回收,因此變數會永遠存在記憶體中,即使當變數不再被使用時,這樣會造成記憶體流失,會嚴重影響頁面的效能。因此當變數對象不再適用時,我們要將其釋放。
我們拿上面代碼舉例:
function foo() { let a = 2; function bar() { console.log(a); } return bar;}let baz = foo();baz();//baz指向的對象會永遠存在堆記憶體中baz = null;//如果baz不在使用,將其指向的對象釋放
對JavaScript中閉包的理解