摘要:中所有的事件綁定都是異步編程當前這件事件沒有徹底完成,不再等待,繼續執行下面的任務當綁定事件后,不需要等待執行,繼續執行下一個循環任務,所以當我們點擊執行方法的時候,循環早已結束即是最后。
概念
閉包就是指有權訪問另一個函數作用域中的變量的函數
點擊li標簽彈出對應數字
如上題,最為常見的一個例子,這里解釋由這道題引出的js知識點,如上我們知道在瀏覽器運行無論點擊哪個li標簽都是彈出3,首先來理解為什么會彈出3。
程序通過for循環給每個li標簽綁定了事件,然后通過點擊li標簽觸發方法,即執行alert(i)。js中有個作用域鏈查找機制,首先會在onclick返回的函數作用域查找i變量的值,找不到則往上一層找i,上一層即是window全局作用域,即找到全局變量i,即for循環定義的i。
注意for循環的i并不是私有變量,而是全局變量。
js中所有的事件綁定都是異步編程(當前這件事件沒有徹底完成,不再等待,繼續執行下面的任務)
當綁定onclick事件后,不需要等待執行,繼續執行下一個循環任務,所以當我們點擊執行方法的時候,循環早已結束,即是最后i=3。故程序執行后全局變量i被循環執行后賦值為最終的3,所以當點擊的時候,外層循環已經結束,頁面加載完成預示著js代碼都已經執行完成,即執行alert(i)時,由于i不是私有變量,便會找到上一級window作用域全局的i,所以無論點擊哪個li標簽都是彈出3
為什么要用閉包那么,解決這個問題的緣由,在于i每次在頁面加載完就賦值為3,alert(i)的時候總是找到全局變量i。在ES5傳統語法中,能形成作用域的只有全局和函數,現在每次i找的都是全局,那么要保住i的值只能在全局和onclick返回函數的作用域中間再加一個小的私有作用域,即是大的作用域外再加一個小的作用域,這樣i往上一層作用域查找時,就會獲取小作用域的i的值,而不會去獲取全局變量的i值。
這個思路解決問題就需要引入閉包,在這個理解上閉包是指函數變量可以保存在函數作用域內,因此看起來是函數將變量“包裹”了起來。于是,代碼改成:
for(var i = 0;i < list.length;i++){ list[i].onclick = (function(n){//形參n //=>讓自執行函數執行,把執行的返回值(return)賦值給onclick //(此處onclick綁定的是返回的小函數,點擊的時候執行的是小函數), // 自執行函數在給事件賦值的時候就已經執行了 // 自執行函數形成一個私有作用域 var i = n; return function(){ alert(i); } })(i);//傳入實參i }
循環三次,形成三個不銷毀的私有作用域(自執行函數執行),而每一個不銷毀的棧內存中都存儲了一個私有變量i,而這個值分別是每一次執行傳遞進來的全局i的值(也就是:第一個不銷毀的作用域存儲的是0,第二個是1,第三個是2,第四個是3);當點擊的時候,執行返回的小函數,遇到變量i,向它自己的上級作用域查找。這樣就達到了我們需要的效果,這種閉包實現,也可以有另一種寫法。
/*原理同法二都是形成三個不銷毀的私有作用域,分別存儲需要的索引值*/ for(var i = 0;i < list.length;i++){ (function(n){ list[n].onclick = function(){ alert(n); } })(i) }
對于初始的代碼,如果說為什么不能實現,那原因就可歸納為:
1.執行方法,形成一個私有的棧內存,遇到變量i,i不是私有變量,向上一級作用域查找(上級作用域window)ES6語法的解決方式
2.所有的事件綁定都是異步編程,綁定事件后,不需要等待執行,繼續執行下一個循環任務,所以當我們點擊執行方法的時候,循環早已結束(讓全局的i等于循環最后的結果3)
在ES6中,解決這種問題只需要一個let變量,ES6中才有塊級作用域(類似于私有作用域)的概念
for(let i = 0;i < list.length;i++){ list[i].onclick = function(){ alert(i); } }閉包對內存的影響
從上面可知,每次for都會形成一個私有作用域,每個都里面保存的變量i的值,程序運行后這些作用域并不會被銷毀,所以由于閉包會攜帶包含它的函數的作用域,所以會比其他函數占用更多內容,過度使用閉包會導致內存占用過多。
在真實項目中為了保證JS的性能(堆棧內存的性能優化),應該盡可能的減少閉包的使用(不銷毀的堆棧內存是耗性能的)
堆內存和棧內存的釋放這里又要提到一個知識點,js中存儲方式的分類:
JS中的內存分為堆內存和棧內存
堆內存:存儲引用數據類型值(對象:鍵值對 函數:代碼字符串)
棧內存:提供JS代碼執行的環境和存儲基本類型值
粗暴理解var定義的變量存在棧內存中,如for循環中的i是存在棧內存中的;而函數,它是存在堆內存中
一般情況下,當函數執行完成,所形成的私有作用域(棧內存)都會自動釋放掉(在棧內存中存儲的值也都會釋放掉),那為什么閉包的棧內存不會被自動釋放掉,在js中也有特殊不被銷毀的情況:
1.函數執行完成,當前形成的棧內存中,某些內容被棧內存以外的變量占用了,此時棧內存不能釋放(一旦釋放外面找不到原有的內容了)
2.全局棧內存只有在頁面關閉的時候才會被釋放掉
閉包則是屬于第一種情況,onclick函數形成的棧內存,被小函數【alert(i),i找到onclick作用域獲取i值】占用了onclick函數的棧內存(變量i是存在棧內存中),故棧內存不能被釋放,所以才會說閉包過度使用容易導致內存被占用過多,因為不會自動釋放內存。
堆內存的釋放
堆內存讓所有引用堆內存空間地址的變量賦值為null即可(沒有變量占用這個堆內存了,瀏覽器會在空閑的時候把它釋放掉)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/106351.html
摘要:引擎對堆內存中的對象進行分代管理新生代存活周期較短的對象,如臨時變量字符串等。內存泄漏對于持續運行的服務進程,必須及時釋放不再用到的內存。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第一期,本周的主題是調用堆棧,今天是第4天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進階計劃...
摘要:深入系列第七篇,結合之前所講的四篇文章,以權威指南的為例,具體講解當函數執行的時候,執行上下文棧變量對象作用域鏈是如何變化的。前言在深入之執行上下文棧中講到,當代碼執行一段可執行代碼時,會創建對應的執行上下文。 JavaScript深入系列第七篇,結合之前所講的四篇文章,以權威指南的demo為例,具體講解當函數執行的時候,執行上下文棧、變量對象、作用域鏈是如何變化的。 前言 在《Jav...
閱讀 3107·2021-10-12 10:20
閱讀 2832·2021-09-27 13:56
閱讀 804·2021-09-27 13:36
閱讀 1442·2021-09-26 09:46
閱讀 2430·2019-08-30 14:02
閱讀 2697·2019-08-28 18:14
閱讀 1276·2019-08-26 10:32
閱讀 1716·2019-08-23 18:25