日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
看完離編寫高性能的JavaScript又近了一步

什么是內存泄露

創(chuàng)新互聯(lián)是一家專業(yè)提供豐南企業(yè)網(wǎng)站建設,專注與成都做網(wǎng)站、網(wǎng)站建設、H5高端網(wǎng)站建設、小程序制作等業(yè)務。10年已為豐南眾多企業(yè)、政府機構等服務。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站制作公司優(yōu)惠進行中。

內存泄漏指由于疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內存。內存泄漏并非指內存在物理上的消失,而是應用程序分配某段內存后,由于設計錯誤,導致在釋放該段內存之前就失去了對該段內存的控制,從而造成了內存的浪費。

內存泄漏通常情況下只能由獲得程序源代碼的程序員才能分析出來。然而,有不少人習慣于把任何不需要的內存使用的增加描述為內存泄漏,即使嚴格意義上來說這是不準確的。

 ————wikipedia

意外的全局變量

JavaScript對未聲明變量的處理方式:在全局對象上創(chuàng)建該變量的引用(即全局對象上的屬性,不是變量,因為它能通過delete刪除)。如果在瀏覽器中,全局對象就是window對象。

如果未聲明的變量緩存大量的數(shù)據(jù),會導致這些數(shù)據(jù)只有在窗口關閉或重新刷新頁面時才能被釋放。這樣會造成意外的內存泄漏。

 
 
 
 
  1. function foo(arg) { 
  2.  
  3.     bar = "this is a hidden global variable with a large of data"; 
  4.  

等同于:

 
 
 
 
  1. function foo(arg) { 
  2.  
  3.     window.bar = "this is an explicit global variable with a large of data"; 
  4.  

另外,通過this創(chuàng)建意外的全局變量:

 
 
 
 
  1. function foo() { 
  2.  
  3.     this.variable = "potential accidental global"; 
  4.  
  5.  
  6. // 當在全局作用域中調用foo函數(shù),此時this指向的是全局對象(window),而不是'undefined' 
  7.  
  8. foo(); 

解決方法:

在JavaScript文件中添加'use strict',開啟嚴格模式,可以有效地避免上述問題。

 
 
 
 
  1. function foo(arg) { 
  2.  
  3.     "use strict" // 在foo函數(shù)作用域內開啟嚴格模式 
  4.  
  5.     bar = "this is an explicit global variable with a large of data";// 報錯:因為bar還沒有被聲明 
  6.  

如果需要在一個函數(shù)中使用全局變量,可以像如下代碼所示,在window上明確聲明:

 
 
 
 
  1. function foo(arg) { 
  2.  
  3.     "use strict" // 在foo函數(shù)作用域內開啟嚴格模式 
  4.  
  5.     bar = "this is an explicit global variable with a large of data";// 報錯:因為bar還沒有被聲明 
  6.  

這樣不僅可讀性高,而且后期維護也方便

談到全局變量,需要注意那些用來臨時存儲大量數(shù)據(jù)的全局變量,確保在處理完這些數(shù)據(jù)后將其設置為null或重新賦值。全局變量也常用來做cache,一般cache都是為了性能優(yōu)化才用到的,為了性能,***對cache的大小做個上限限制。因為cache是不能被回收的,越高cache會導致越高的內存消耗。

console.log

console.log:向web開發(fā)控制臺打印一條消息,常用來在開發(fā)時調試分析。有時在開發(fā)時,需要打印一些對象信息,但發(fā)布時卻忘記去掉console.log語句,這可能造成內存泄露。

在傳遞給console.log的對象是不能被垃圾回收 ?,因為在代碼運行之后需要在開發(fā)工具能查看對象信息。所以***不要在生產環(huán)境中console.log任何對象。

實例------>demos/log.html

 
 
 
 
  1.  
  2.  
  3.  
  4.  
  5.    
  6.    
  7.    
  8.   Leaker 
  9.  
  10.  
  11.  
  12.    
  13.    
  14.  
  15.  
  16.  

這里結合Chrome的Devtools–>Performance做一些分析,操作步驟如下:

?注:***在隱藏窗口中進行分析工作,避免瀏覽器插件影響分析結果

  1. 開啟【Performance】項的記錄
  2. 執(zhí)行一次CG,創(chuàng)建基準參考線
  3. 連續(xù)單擊【click】按鈕三次,新建三個Leaker對象
  4. 執(zhí)行一次CG
  5. 停止記錄

可以看出【JS Heap】線***沒有降回到基準參考線的位置,顯然存在沒有被回收的內存。如果將代碼修改為:

 
 
 
 
  1. !function () { 
  2.       function Leaker() { 
  3.         this.init(); 
  4.       }; 
  5.       Leaker.prototype = { 
  6.         init: function () { 
  7.           this.name = (Array(100000)).join('*'); 
  8.         }, 
  9.  
  10.         destroy: function () { 
  11.           // do something.... 
  12.         } 
  13.       }; 
  14.       document.querySelector('input').addEventListener('click', function () { 
  15.         new Leaker(); 
  16.       }, false); 
  17.     }() 

去掉console.log("Leaking an object %o: %o", (new Date()), this);語句。重復上述的操作步驟,分析結果如下:

從對比分析結果可知,console.log打印的對象是不會被垃圾回收器回收的。因此***不要在頁面中console.log任何大對象,這樣可能會影響頁面的整體性能,特別在生產環(huán)境中。除了console.log外,另外還有console.dir、console.error、console.warn等都存在類似的問題,這些細節(jié)需要特別的關注。

closures(閉包)

當一個函數(shù)A返回一個內聯(lián)函數(shù)B,即使函數(shù)A執(zhí)行完,函數(shù)B也能訪問函數(shù)A作用域內的變量,這就是一個閉包——————本質上閉包是將函數(shù)內部和外部連接起來的一座橋梁。

 
 
 
 
  1. function foo(message) { 
  2.  
  3.     function closure() { 
  4.  
  5.         console.log(message) 
  6.  
  7.     }; 
  8.  
  9.     return closure; 
  10.  
  11.  
  12. // 使用 
  13.  
  14. var bar = foo("hello closure!"); 
  15.  
  16. bar()// 返回 'hello closure!' 

在函數(shù)foo內創(chuàng)建的函數(shù)closure對象是不能被回收掉的,因為它被全局變量bar引用,處于一直可訪問狀態(tài)。通過執(zhí)行bar()可以打印出hello closure!。如果想釋放掉可以將bar = null即可。

由于閉包會攜帶包含它的函數(shù)的作用域,因此會比其他函數(shù)占用更多的內存。過度使用閉包可能會導致內存占用過多。

實例------>demos/closures.html

 
 
 
 
  1.  
  2.  
  3.  
  4.  
  5.    
  6.    
  7.    
  8.   Closure 
  9.  
  10.  
  11.  
  12.   

    不斷單擊【click】按鈕

     
  13.   Click 
  14.    
  15.  
  16.  
  17.  

這里結合Chrome的Devtools->Memory工具進行分析,操作步驟如下:

?注:***在隱藏窗口中進行分析工作,避免瀏覽器插件影響分析結果

  1. 選中【Record allocation timeline】選項
  2. 執(zhí)行一次CG
  3. 單擊【start】按鈕開始記錄堆分析
  4. 連續(xù)單擊【click】按鈕十多次
  5. 停止記錄堆分析

上圖中藍色柱形條表示隨著時間新分配的內存。選中其中某條藍色柱形條,過濾出對應新分配的對象:

查看對象的詳細信息:

從圖可知,在返回的閉包作用鏈(Scopes)中攜帶有它所在函數(shù)的作用域,作用域中還包含一個str字段。而str字段并沒有在返回getData()中使用過。為什么會存在在作用域中,按理應該被GC回收掉, whyquestion

原因是在相同作用域內創(chuàng)建的多個內部函數(shù)對象是共享同一個變量對象(variable object)。如果創(chuàng)建的內部函數(shù)沒有被其他對象引用,不管內部函數(shù)是否引用外部函數(shù)的變量和函數(shù),在外部函數(shù)執(zhí)行完,對應變量對象便會被銷毀。反之,如果內部函數(shù)中存在有對外部函數(shù)變量或函數(shù)的訪問(可以不是被引用的內部函數(shù)),并且存在某個或多個內部函數(shù)被其他對象引用,那么就會形成閉包,外部函數(shù)的變量對象就會存在于閉包函數(shù)的作用域鏈中。這樣確保了閉包函數(shù)有權訪問外部函數(shù)的所有變量和函數(shù)。了解了問題產生的原因,便可以對癥下藥了。對代碼做如下修改:

 
 
 
 
  1. function f() { 
  2.  
  3.   var str = Array(10000).join('#'); 
  4.  
  5.   var foo = { 
  6.  
  7.     name: 'foo' 
  8.  
  9.   } 
  10.  
  11.   function unused() { 
  12.  
  13.     var message = 'it is only a test message'; 
  14.  
  15.     // str = 'unused: ' + str; //刪除該條語句 
  16.  
  17.   } 
  18.  
  19.   function getData() { 
  20.  
  21.     return 'data'; 
  22.  
  23.   } 
  24.  
  25.   return getData; 
  26.  
  27.  
  28. var list = [];     
  29.  
  30. document.querySelector('#click_button').addEventListener('click', function () { 
  31.  
  32.   list.push(f()); 
  33.  
  34. }, false); 

getData()和unused()內部函數(shù)共享f函數(shù)對應的變量對象,因為unused()內部函數(shù)訪問了f作用域內str變量,所以str字段存在于f變量對象中。加上getData()內部函數(shù)被返回,被其他對象引用,形成了閉包,因此對應的f變量對象存在于閉包函數(shù)的作用域鏈中。這里只要將函數(shù)unused中str = 'unused: ' + str;語句刪除便可解決問題。

查看一下閉包信息:

DOM泄露

在JavaScript中,DOM操作是非常耗時的。因為JavaScript/ECMAScript引擎獨立于渲染引擎,而DOM是位于渲染引擎,相互訪問需要消耗一定的資源。如Chrome瀏覽器中DOM位于WebCore,而JavaScript/ECMAScript位于V8中。假如將JavaScript/ECMAScript、DOM分別想象成兩座孤島,兩島之間通過一座收費橋連接,過橋需要交納一定“過橋費”。JavaScript/ECMAScript每次訪問DOM時,都需要交納“過橋費”。因此訪問DOM次數(shù)越多,費用越高,頁面性能就會受到很大影響。了解更多??

為了減少DOM訪問次數(shù),一般情況下,當需要多次訪問同一個DOM方法或屬性時,會將DOM引用緩存到一個局部變量中。但如果在執(zhí)行某些刪除、更新操作后,可能會忘記釋放掉代碼中對應的DOM引用,這樣會造成DOM內存泄露。

實例------>demos/dom.html

 
 
 
 
  1.  
  2.  
  3.  
  4.    
  5.    
  6.    
  7.   Dom-Leakage 
  8.  
  9.  
  10.    
  11.    
  12.  
  13.    
  14.     
 
  •   
  •  
  •    
  •  
  •  
  • 這里結合Chrome瀏覽器的Devtools–>Performance做一些分析,操作步驟如下:

    ?注:***在隱藏窗口中進行分析工作,避免瀏覽器插件影響分析結果

    1. 開啟【Performance】項的記錄
    2. 執(zhí)行一次CG,創(chuàng)建基準參考線
    3. 連續(xù)單擊【add】按鈕6次,增加6個文本節(jié)點到pre元素中
    4. 單擊【remove】按鈕,刪除剛增加6個文本節(jié)點和pre元元素
    5. 執(zhí)行一次CG
    6. 停止記錄堆分析

    從分析結果圖可知,雖然6次add操作增加6個Node,但是remove操作并沒有讓Nodes節(jié)點數(shù)下降,即remove操作失敗。盡管還主動執(zhí)行了一次CG操作,Nodes曲線也沒有下降。因此可以斷定內存泄露了!那問題來了,如何去查找問題的原因呢?這里可以通過Chrome瀏覽器的Devtools–>Memory進行診斷分析,執(zhí)行如下操作步驟:

    ?注:***在隱藏窗口中進行分析工作,避免瀏覽器插件影響分析結果

    1. 選中【Take heap snapshot】選項
    2. 連續(xù)單擊【add】按鈕6次,增加6個文本節(jié)點到pre元素中
    3. 單擊【Take snapshot】按鈕,執(zhí)行一次堆快照
    4. 單擊【remove】按鈕,刪除剛增加6個文本節(jié)點和pre元元素
    5. 單擊【Take snapshot】按鈕,執(zhí)行一次堆快照
    6. 選中生成的第二個快照報告,并將視圖由"Summary"切換到"Comparison"對比模式,在[class filter]過濾輸入框中輸入關鍵字:Detached

    從分析結果圖可知,導致整個pre元素和6個文本節(jié)點無法別回收的原因是:代碼中存在全局變量wrapper對pre元素的引用。知道了產生的問題原因,便可對癥下藥了。對代碼做如下就修改:

     
     
     
     
    1. // 因為要多次用到pre.wrapper、div.container、input.remove、input.add節(jié)點,將其緩存到本地變量中 
    2.  
    3. var wrapper = document.querySelector('.wrapper'); 
    4.  
    5. var container = document.querySelector('.container'); 
    6.  
    7. var removeBtn = document.querySelector('.remove'); 
    8.  
    9. var addBtn = document.querySelector('.add'); 
    10.  
    11. var counter = 0; 
    12.  
    13. var once = true; 
    14.  
    15. // 方法 
    16.  
    17. var hide = function(target){ 
    18.  
    19.   target.style.display = 'none'; 
    20.  
    21.  
    22. var show = function(target){ 
    23.  
    24.   target.style.display = 'inline-block'; 
    25.  
    26.  
    27. // 回調函數(shù) 
    28.  
    29. var removeCallback = function(){ 
    30.  
    31.   removeBtn.removeEventListener('click', removeCallback, false); 
    32.  
    33.   addBtn.removeEventListener('click', addCallback, false); 
    34.  
    35.   hide(addBtn); 
    36.  
    37.   hide(removeBtn); 
    38.  
    39.   container.removeChild(wrapper);  
    40.   
    41.  
    42.   wrapper = null;//在執(zhí)行刪除操作時,將wrapper對pre節(jié)點的引用釋放掉 
    43.  
    44.  
    45. var addCallback = function(){ 
    46.  
    47.   wrapper.appendChild(document.createTextNode('\t' + ++counter + ':a new line text\n')); 
    48.  
    49.   // 顯示刪除操作按鈕 
    50.  
    51.   if(once){ 
    52.  
    53.     show(removeBtn); 
    54.  
    55.     once = false; 
    56.  
    57.   } 
    58.  
    59.  
    60. // 綁定事件 
    61.  
    62. removeBtn.addEventListener('click', removeCallback, false); 
    63.  
    64. addBtn.addEventListener('click', addCallback, false); 

    在執(zhí)行刪除操作時,將wrapper對pre節(jié)點的引用釋放掉,即在刪除邏輯中增加wrapper = null;語句。再次在Devtools–>Performance中重復上述操作:

    小試牛刀------>demos/dom_practice.html

    再來看看網(wǎng)上的一個實例,代碼如下:

     
     
     
     
    1.  
    2.  
    3.  
    4.  
    5.  
    6.  
    7.    
    8.  
    9.    
    10.  
    11.    
    12.  
    13.   Practice 
    14.  
    15.  
    16.  
    17.  
    18.  
    19.   
     
  •  
  •   
     
  •  
  •   
     
  •  
  •  
  •  
  •    
  •  
  •  
  •  
  •  
  • 整個過程如下圖所演示:

    有興趣的同學可以使用Chrome的Devtools工具,驗證一下分析結果,實踐很重要~~~high_brightness

    timers

    在JavaScript常用setInterval()來實現(xiàn)一些動畫效果。當然也可以使用鏈式setTimeout()調用模式來實現(xiàn):

     
     
     
     
    1. setTimeout(function() { 
    2.   // do something. . . . 
    3.   setTimeout(arguments.callee, interval); 
    4. }, interval); 

    如果在不需要setInterval()時,沒有通過clearInterval()方法移除,那么setInterval()會不停地調用函數(shù),直到調用clearInterval()或窗口關閉。如果鏈式setTimeout()調用模式?jīng)]有給出終止邏輯,也會一直運行下去。因此再不需要重復定時器時,確保對定時器進行清除,避免占用系統(tǒng)資源。另外,在使用setInterval()和setTimeout()來實現(xiàn)動畫時,無法確保定時器按照指定的時間間隔來執(zhí)行動畫。為了能在JavaScript中創(chuàng)建出平滑流暢的動畫,瀏覽器為JavaScript動畫添加了一個新API-requestAnimationFrame()。關于setInterval、setTimeout與requestAnimationFrame實現(xiàn)動畫上的區(qū)別猛擊

    實例------>demos/timers.html

    如下通過setInterval()實現(xiàn)一個clock的小實例,不過代碼存在問題的,有興趣的同學可以先嘗試找一下問題的所在~

    操作:

    • 單擊【start】按鈕開始clock,同時web開發(fā)控制臺會打印實時信息
    • 單擊【stop】按鈕停止clock,同時web開發(fā)控制臺會輸出停止信息

     
     
     
     
    1.  
    2.  
    3.  
    4.  
    5.  
    6.  
    7.    
    8.  
    9.    
    10.  
    11.    
    12.  
    13.   setInterval 
    14.  
    15.  
    16.  
    17.  
    18.  
    19.    
    20.  
    21.     
    22.  
    23.  
    24.    
    25.  
    26.  
    27.  
    28.  

    上述代碼存在兩個問題:

    1. 如果不斷的單擊【start】按鈕,會斷生成新的clock。
    2. 單擊【stop】按鈕不能停止clock。

    輸出結果:

    針對暴露出的問題,對代碼做如下修改:

     
     
     
     
    1. var counter = 0; 
    2.  
    3. var clock = { 
    4.  
    5.   timer: null, 
    6.  
    7.   start: function () { 
    8.  
    9.     // 解決***個問題 
    10.  
    11.     if (this.timer) { 
    12.  
    13.       clearInterval(this.timer); 
    14.  
    15.     } 
    16.  
    17.     this.timer = setInterval(this.step.bind(null, ++counter), 1000); 
    18.  
    19.   }, 
    20.  
    21.   step: function (flag) { 
    22.  
    23.     var date = new Date(); 
    24.  
    25.     var h = date.getHours(); 
    26.  
    27.     var m = date.getMinutes(); 
    28.  
    29.     var s = date.getSeconds(); 
    30.  
    31.     console.log("%d-----> %d:%d:%d", flag, h, m, s); 
    32.  
    33.   }, 
    34.  
    35.   // 解決第二個問題 
    36.  
    37.   destroy: function () { 
    38.  
    39.     console.log('----> stop <----'); 
    40.  
    41.     clearInterval(this.timer); 
    42.  
    43.     node = null; 
    44.  
    45.     counter = void(0); 
    46.  
    47.   } 
    48.  
    49.  
    50. document.querySelector('.start').addEventListener('click', clock.start.bind(clock), false); 
    51.  
    52. document.querySelector('.stop').addEventListener('click', clock.destroy.bind(clock), false); 

    EventListener

    做移動開發(fā)時,需要對不同設備尺寸做適配。如在開發(fā)組件時,有時需要考慮處理橫豎屏適配問題。一般做法,在橫豎屏發(fā)生變化時,需要將組件銷毀后再重新生成。而在組件中會對其進行相關事件綁定,如果在銷毀組件時,沒有將組件的事件解綁,在橫豎屏發(fā)生變化時,就會不斷地對組件進行事件綁定。這樣會導致一些異常,甚至可能會導致頁面崩掉。

    實例------>demos/callbacks.html

     
     
     
     
    1.  
    2.  
    3.  
    4.  
    5.  
    6.  
    7.    
    8.  
    9.    
    10.  
    11.    
    12.  
    13.   callbacks 
    14.  
    15.  
    16.  
    17.  
    18.  
    19.   
     
  •  
  •    
  •  
  •  
  •  
  •  
  • 頁面是存在問題的,這里結合Devtools–>Performance分析一下問題所在,操作步驟如下:

    ?注:***在隱藏窗口中進行分析工作,避免瀏覽器插件影響分析結果

    1. 開啟Performance項的記錄
    2. 執(zhí)行一次CG,創(chuàng)建基準參考線
    3. 對窗口大小進行調整
    4. 執(zhí)行一次CG
    5. 停止記錄

    如分析結果所示,在窗口大小變化時,會不斷地對container添加代理事件。

    同一個元素節(jié)點注冊了多個相同的EventListener,那么重復的實例會被拋棄。這么做不會讓得EventListener被重復調用,也不需要用removeEventListener手動清除多余的EventListener,因為重復的都被自動拋棄了。而這條規(guī)則只是針對于命名函數(shù)。對于匿名函數(shù),瀏覽器會將其看做不同的EventListener,所以只要將匿名的EventListener,命名一下就可以解決問題:

     
     
     
     
    1. var container = document.querySelector('.container'); 
    2.     var counter = 0; 
    3.     var createHtml = function (n, counter) { 
    4.       var template = `${(new Array(n)).join(`
      ${counter}: this is a new data 
      `)}` 
    5.       container.innerHTML = template; 
    6.     } 
    7.     //  
    8.     var clickCallback = function (event) { 
    9.       var target = event.target; 
    10.       if (target.tagName === 'INPUT') { 
    11.         container.removeChild(target.parentElement) 
    12.       } 
    13.     } 
    14.     var resizeCallback = function (init) { 
    15.       createHtml(10, ++counter); 
    16.       // 事件委托 
    17.       container.addEventListener('click', clickCallback, false); 
    18.     } 
    19.     window.addEventListener('resize', resizeCallback, false); 
    20.     resizeCallback(true); 

    在Devtools–>Performance中再重復上述操作,分析結果如下:

    在開發(fā)中,開發(fā)者很少關注事件解綁,因為瀏覽器已經(jīng)為我們處理得很好了。不過在使用第三方庫時,需要特別注意,因為一般第三方庫都實現(xiàn)了自己的事件綁定,如果在使用過程中,在需要銷毀事件綁定時,沒有調用所解綁方法,就可能造成事件綁定數(shù)量的不斷增加。如下鏈接是我在項目中使用jquery,遇見到類似問題:jQuery中忘記解綁注冊的事件,造成內存泄露猛擊

    總結

    本文主要介紹了幾種常見的內存泄露。在開發(fā)過程,需要我們特別留意一下本文所涉及到的幾種內存泄露問題。因為這些隨時可能發(fā)生在我們日常開發(fā)中,如果我們對它們不了解是很難發(fā)現(xiàn)它們的存在??赡茉谒鼈儗栴}影響程度放大時,才會引起我們的關注。不過那時可能就晚了,因為產品可能已經(jīng)上線,接著就會嚴重影響產品的質量和用戶體驗,甚至可能讓我們承受大量用戶流失的損失。作為開發(fā)的我們必須把好這個關,讓我們開發(fā)的產品帶給用戶***的體驗。

    參考文章:

    • An interesting kind of JavaScript memory leak
    • Memory Leaks in Microsoft Internet Explorer
    • Memory leak when logging complex objects 

    分享名稱:看完離編寫高性能的JavaScript又近了一步
    文章URL:http://www.dlmjj.cn/article/cdeoepi.html