自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

看完離編寫高性能的JavaScript又近了一步

開發(fā) 前端
內(nèi)存泄漏指由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存。內(nèi)存泄漏并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)計(jì)錯(cuò)誤,導(dǎo)致在釋放該段內(nèi)存之前就失去了對(duì)該段內(nèi)存的控制,從而造成了內(nèi)存的浪費(fèi)。

 

[[212466]]

什么是內(nèi)存泄露

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

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

 ————wikipedia

意外的全局變量

JavaScript對(duì)未聲明變量的處理方式:在全局對(duì)象上創(chuàng)建該變量的引用(即全局對(duì)象上的屬性,不是變量,因?yàn)樗芡ㄟ^(guò)delete刪除)。如果在瀏覽器中,全局對(duì)象就是window對(duì)象。

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

  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.  

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

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

解決方法:

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

  1. function foo(arg) { 
  2.  
  3.     "use strict" // 在foo函數(shù)作用域內(nèi)開啟嚴(yán)格模式 
  4.  
  5.     bar = "this is an explicit global variable with a large of data";// 報(bào)錯(cuò):因?yàn)閎ar還沒(méi)有被聲明 
  6.  

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

  1. function foo(arg) { 
  2.  
  3.     "use strict" // 在foo函數(shù)作用域內(nèi)開啟嚴(yán)格模式 
  4.  
  5.     bar = "this is an explicit global variable with a large of data";// 報(bào)錯(cuò):因?yàn)閎ar還沒(méi)有被聲明 
  6.  

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

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

console.log

console.log:向web開發(fā)控制臺(tái)打印一條消息,常用來(lái)在開發(fā)時(shí)調(diào)試分析。有時(shí)在開發(fā)時(shí),需要打印一些對(duì)象信息,但發(fā)布時(shí)卻忘記去掉console.log語(yǔ)句,這可能造成內(nèi)存泄露。

在傳遞給console.log的對(duì)象是不能被垃圾回收 ♻️,因?yàn)樵诖a運(yùn)行之后需要在開發(fā)工具能查看對(duì)象信息。所以***不要在生產(chǎn)環(huán)境中console.log任何對(duì)象。

實(shí)例------>demos/log.html

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3.  
  4. <head> 
  5.   <meta charset="UTF-8"
  6.   <meta name="viewport" content="width=device-width, initial-scale=1.0"
  7.   <meta http-equiv="X-UA-Compatible" content="ie=edge"
  8.   <title>Leaker</title> 
  9. </head> 
  10.  
  11. <body> 
  12.   <input type="button" value="click"
  13.   <script> 
  14.     !function () { 
  15.       function Leaker() { 
  16.         this.init(); 
  17.       }; 
  18.       Leaker.prototype = { 
  19.         init: function () { 
  20.           this.name = (Array(100000)).join('*'); 
  21.           console.log("Leaking an object %o: %o", (new Date()), this);// this對(duì)象不能被回收 
  22.         }, 
  23.  
  24.         destroy: function () { 
  25.           // do something.... 
  26.         } 
  27.       }; 
  28.       document.querySelector('input').addEventListener('click'function () { 
  29.         new Leaker(); 
  30.       }, false); 
  31.     }() 
  32.   </script> 
  33. </body> 
  34.  
  35. </html> 

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

⚠️注:***在隱藏窗口中進(jìn)行分析工作,避免瀏覽器插件影響分析結(jié)果

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

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

  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);語(yǔ)句。重復(fù)上述的操作步驟,分析結(jié)果如下:

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

closures(閉包)

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

  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內(nèi)創(chuàng)建的函數(shù)closure對(duì)象是不能被回收掉的,因?yàn)樗蝗肿兞縝ar引用,處于一直可訪問(wèn)狀態(tài)。通過(guò)執(zhí)行bar()可以打印出hello closure!。如果想釋放掉可以將bar = null即可。

由于閉包會(huì)攜帶包含它的函數(shù)的作用域,因此會(huì)比其他函數(shù)占用更多的內(nèi)存。過(guò)度使用閉包可能會(huì)導(dǎo)致內(nèi)存占用過(guò)多。

實(shí)例------>demos/closures.html

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3.  
  4. <head> 
  5.   <meta charset="UTF-8"
  6.   <meta name="viewport" content="width=device-width, initial-scale=1.0"
  7.   <meta http-equiv="X-UA-Compatible" content="ie=edge"
  8.   <title>Closure</title> 
  9. </head> 
  10.  
  11. <body> 
  12.   <p>不斷單擊【click】按鈕</p> 
  13.   <button id="click_button">Click</button> 
  14.   <script> 
  15.     function f() { 
  16.       var str = Array(10000).join('#'); 
  17.       var foo = { 
  18.         name'foo' 
  19.       } 
  20.       function unused() { 
  21.         var message = 'it is only a test message'
  22.         str = 'unused: ' + str; 
  23.       } 
  24.       function getData() { 
  25.         return 'data'
  26.       } 
  27.       return getData; 
  28.     } 
  29.  
  30.     var list = []; 
  31.      
  32.     document.querySelector('#click_button').addEventListener('click'function () { 
  33.       list.push(f()); 
  34.     }, false); 
  35.   </script> 
  36. </body> 
  37.  
  38. </html> 

這里結(jié)合Chrome的Devtools->Memory工具進(jìn)行分析,操作步驟如下:

⚠️注:***在隱藏窗口中進(jìn)行分析工作,避免瀏覽器插件影響分析結(jié)果

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

上圖中藍(lán)色柱形條表示隨著時(shí)間新分配的內(nèi)存。選中其中某條藍(lán)色柱形條,過(guò)濾出對(duì)應(yīng)新分配的對(duì)象:

查看對(duì)象的詳細(xì)信息:

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

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

 

  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; //刪除該條語(yǔ)句 
  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()內(nèi)部函數(shù)共享f函數(shù)對(duì)應(yīng)的變量對(duì)象,因?yàn)閡nused()內(nèi)部函數(shù)訪問(wèn)了f作用域內(nèi)str變量,所以str字段存在于f變量對(duì)象中。加上getData()內(nèi)部函數(shù)被返回,被其他對(duì)象引用,形成了閉包,因此對(duì)應(yīng)的f變量對(duì)象存在于閉包函數(shù)的作用域鏈中。這里只要將函數(shù)unused中str = 'unused: ' + str;語(yǔ)句刪除便可解決問(wèn)題。

查看一下閉包信息:

DOM泄露

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

[[212467]]

為了減少DOM訪問(wèn)次數(shù),一般情況下,當(dāng)需要多次訪問(wèn)同一個(gè)DOM方法或?qū)傩詴r(shí),會(huì)將DOM引用緩存到一個(gè)局部變量中。但如果在執(zhí)行某些刪除、更新操作后,可能會(huì)忘記釋放掉代碼中對(duì)應(yīng)的DOM引用,這樣會(huì)造成DOM內(nèi)存泄露。

實(shí)例------>demos/dom.html

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.   <meta charset="UTF-8"
  5.   <meta name="viewport" content="width=device-width, initial-scale=1.0"
  6.   <meta http-equiv="X-UA-Compatible" content="ie=edge"
  7.   <title>Dom-Leakage</title> 
  8. </head> 
  9. <body> 
  10.   <input type="button" value="add" class="add"
  11.   <input type="button" value="remove" class="remove" style="display:none;"
  12.  
  13.   <div class="container"
  14.     <pre class="wrapper"></pre> 
  15.   </div> 
  16.   <script> 
  17.     // 因?yàn)橐啻斡玫絧re.wrapper、div.container、input.remove、input.add節(jié)點(diǎn),將其緩存到本地變量中 
  18.     var wrapper = document.querySelector('.wrapper'); 
  19.     var container = document.querySelector('.container'); 
  20.     var removeBtn = document.querySelector('.remove'); 
  21.     var addBtn = document.querySelector('.add'); 
  22.     var counter = 0; 
  23.     var once = true
  24.     // 方法 
  25.     var hide = function(target){ 
  26.       target.style.display = 'none'
  27.     } 
  28.     var show = function(target){ 
  29.       target.style.display = 'inline-block'
  30.     } 
  31.     // 回調(diào)函數(shù) 
  32.     var removeCallback = function(){ 
  33.       removeBtn.removeEventListener('click', removeCallback, false); 
  34.       addBtn.removeEventListener('click', addCallback, false); 
  35.       hide(addBtn); 
  36.       hide(removeBtn); 
  37.       container.removeChild(wrapper); 
  38.     } 
  39.     var addCallback = function(){ 
  40.       wrapper.appendChild(document.createTextNode('\t' + ++counter + ':a new line text\n')); 
  41.       // 顯示刪除操作按鈕 
  42.       if(once){ 
  43.         show(removeBtn); 
  44.         once = false
  45.       } 
  46.     } 
  47.     // 綁定事件 
  48.     removeBtn.addEventListener('click', removeCallback, false); 
  49.     addBtn.addEventListener('click', addCallback, false); 
  50.   </script> 
  51. </body> 
  52. </html> 

這里結(jié)合Chrome瀏覽器的Devtools–>Performance做一些分析,操作步驟如下:

⚠️注:***在隱藏窗口中進(jìn)行分析工作,避免瀏覽器插件影響分析結(jié)果

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

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

⚠️注:***在隱藏窗口中進(jìn)行分析工作,避免瀏覽器插件影響分析結(jié)果

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

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

 

  1. // 因?yàn)橐啻斡玫絧re.wrapper、div.container、input.remove、input.add節(jié)點(diǎn),將其緩存到本地變量中 
  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. // 回調(diào)函數(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í)行刪除操作時(shí),將wrapper對(duì)pre節(jié)點(diǎn)的引用釋放掉 
  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í)行刪除操作時(shí),將wrapper對(duì)pre節(jié)點(diǎn)的引用釋放掉,即在刪除邏輯中增加wrapper = null;語(yǔ)句。再次在Devtools–>Performance中重復(fù)上述操作:

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

再來(lái)看看網(wǎng)上的一個(gè)實(shí)例,代碼如下:

 

  1. <!DOCTYPE html> 
  2.  
  3. <html lang="en"
  4.  
  5. <head> 
  6.  
  7.   <meta charset="UTF-8"
  8.  
  9.   <meta name="viewport" content="width=device-width, initial-scale=1.0"
  10.  
  11.   <meta http-equiv="X-UA-Compatible" content="ie=edge"
  12.  
  13.   <title>Practice</title> 
  14.  
  15. </head> 
  16.  
  17. <body> 
  18.  
  19.   <div id="refA"><ul><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#" id="refB"></a></li></ul></div> 
  20.  
  21.   <div></div> 
  22.  
  23.   <div></div> 
  24.  
  25.  
  26.  
  27.   <script> 
  28.  
  29.     var refA = document.getElementById('refA'); 
  30.  
  31.     var refB = document.getElementById('refB'); 
  32.  
  33.     document.body.removeChild(refA); 
  34.  
  35.  
  36.  
  37.     // #refA不能GC回收,因?yàn)榇嬖谧兞縭efA對(duì)它的引用。將其對(duì)#refA引用釋放,但還是無(wú)法回收#refA。 
  38.  
  39.     refA = null
  40.  
  41.     // 還存在變量refB對(duì)#refA的間接引用(refB引用了#refB,而#refB屬于#refA)。將變量refB對(duì)#refB的引用釋放,#refA就可以被GC回收。 
  42.  
  43.     refB = null
  44.  
  45.   </script> 
  46.  
  47. </body> 
  48.  
  49. </html> 

整個(gè)過(guò)程如下圖所演示:

[[212468]]

有興趣的同學(xué)可以使用Chrome的Devtools工具,驗(yàn)證一下分析結(jié)果,實(shí)踐很重要~~~high_brightness

timers

在JavaScript常用setInterval()來(lái)實(shí)現(xiàn)一些動(dòng)畫效果。當(dāng)然也可以使用鏈?zhǔn)絪etTimeout()調(diào)用模式來(lái)實(shí)現(xiàn):

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

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

實(shí)例------>demos/timers.html

如下通過(guò)setInterval()實(shí)現(xiàn)一個(gè)clock的小實(shí)例,不過(guò)代碼存在問(wèn)題的,有興趣的同學(xué)可以先嘗試找一下問(wèn)題的所在~😎

操作:

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

 

  1. <!DOCTYPE html> 
  2.  
  3. <html lang="en"
  4.  
  5. <head> 
  6.  
  7.   <meta charset="UTF-8"
  8.  
  9.   <meta name="viewport" content="width=device-width, initial-scale=1.0"
  10.  
  11.   <meta http-equiv="X-UA-Compatible" content="ie=edge"
  12.  
  13.   <title>setInterval</title> 
  14.  
  15. </head> 
  16.  
  17. <body> 
  18.  
  19.   <input type="button" value="start" class="start"
  20.  
  21.   <input type="button" value="stop" class="stop">  
  22.  
  23.  
  24.   <script> 
  25.  
  26.     var counter = 0; 
  27.  
  28.     var clock = { 
  29.  
  30.       start: function () { 
  31.  
  32.         setInterval(this.step.bind(null, ++counter), 1000); 
  33.  
  34.       }, 
  35.  
  36.       step: function (flag) { 
  37.  
  38.         var date = new Date(); 
  39.  
  40.         var h = date.getHours(); 
  41.  
  42.         var m = date.getMinutes(); 
  43.  
  44.         var s = date.getSeconds(); 
  45.  
  46.         console.log("%d-----> %d:%d:%d", flag, h, m, s); 
  47.  
  48.       } 
  49.  
  50.     } 
  51.  
  52.     document.querySelector('.start').addEventListener('click', clock.start.bind(clock), false); 
  53.  
  54.     document.querySelector('.stop').addEventListener('click'function () { 
  55.  
  56.       console.log('----> stop <----'); 
  57.  
  58.       clock = null
  59.  
  60.     }, false); 
  61.  
  62.   </script> 
  63.  
  64. </body> 
  65.  
  66. </html> 

上述代碼存在兩個(gè)問(wèn)題:

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

輸出結(jié)果:

針對(duì)暴露出的問(wèn)題,對(duì)代碼做如下修改:

 

  1. var counter = 0; 
  2.  
  3. var clock = { 
  4.  
  5.   timer: null
  6.  
  7.   start: function () { 
  8.  
  9.     // 解決***個(gè)問(wèn)題 
  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.   // 解決第二個(gè)問(wèn)題 
  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

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

實(shí)例------>demos/callbacks.html

 

  1. <!DOCTYPE html> 
  2.  
  3. <html lang="en"
  4.  
  5. <head> 
  6.  
  7.   <meta charset="UTF-8"
  8.  
  9.   <meta name="viewport" content="width=device-width, initial-scale=1.0"
  10.  
  11.   <meta http-equiv="X-UA-Compatible" content="ie=edge"
  12.  
  13.   <title>callbacks</title> 
  14.  
  15. </head> 
  16.  
  17. <body> 
  18.  
  19.   <div class="container"></div> 
  20.  
  21.   <script> 
  22.  
  23.     var container = document.querySelector('.container'); 
  24.  
  25.     var counter = 0; 
  26.  
  27.     var createHtml = function (n, counter) { 
  28.  
  29.       var template = `${(new Array(n)).join(`<div>${counter}: this is a new data <input type="button" value="remove"></div>`)}` 
  30.  
  31.       container.innerHTML = template; 
  32.  
  33.     } 
  34.  
  35.     
  36.  
  37.     var resizeCallback = function (init) { 
  38.  
  39.       createHtml(10, ++counter); 
  40.  
  41.       // 事件委托 
  42.  
  43.       container.addEventListener('click'function (event){ 
  44.  
  45.         var target = event.target; 
  46.  
  47.           if(target.tagName === 'INPUT'){ 
  48.  
  49.               container.removeChild(target.parentElement) 
  50.  
  51.           } 
  52.  
  53.       }, false);    
  54.  
  55.     } 
  56.  
  57.     window.addEventListener('resize', resizeCallback, false); 
  58.  
  59.     resizeCallback(true); 
  60.  
  61.   </script> 
  62.  
  63. </body> 
  64.  
  65. </html> 

頁(yè)面是存在問(wèn)題的,這里結(jié)合Devtools–>Performance分析一下問(wèn)題所在,操作步驟如下:

⚠️注:***在隱藏窗口中進(jìn)行分析工作,避免瀏覽器插件影響分析結(jié)果

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

如分析結(jié)果所示,在窗口大小變化時(shí),會(huì)不斷地對(duì)container添加代理事件。

同一個(gè)元素節(jié)點(diǎn)注冊(cè)了多個(gè)相同的EventListener,那么重復(fù)的實(shí)例會(huì)被拋棄。這么做不會(huì)讓得EventListener被重復(fù)調(diào)用,也不需要用removeEventListener手動(dòng)清除多余的EventListener,因?yàn)橹貜?fù)的都被自動(dòng)拋棄了。而這條規(guī)則只是針對(duì)于命名函數(shù)。對(duì)于匿名函數(shù),瀏覽器會(huì)將其看做不同的EventListener,所以只要將匿名的EventListener,命名一下就可以解決問(wèn)題:

  1. var container = document.querySelector('.container'); 
  2.     var counter = 0; 
  3.     var createHtml = function (n, counter) { 
  4.       var template = `${(new Array(n)).join(`<div>${counter}: this is a new data <input type="button" value="remove"></div>`)}` 
  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ù)上述操作,分析結(jié)果如下:

在開發(fā)中,開發(fā)者很少關(guān)注事件解綁,因?yàn)闉g覽器已經(jīng)為我們處理得很好了。不過(guò)在使用第三方庫(kù)時(shí),需要特別注意,因?yàn)橐话愕谌綆?kù)都實(shí)現(xiàn)了自己的事件綁定,如果在使用過(guò)程中,在需要銷毀事件綁定時(shí),沒(méi)有調(diào)用所解綁方法,就可能造成事件綁定數(shù)量的不斷增加。如下鏈接是我在項(xiàng)目中使用jquery,遇見到類似問(wèn)題:jQuery中忘記解綁注冊(cè)的事件,造成內(nèi)存泄露➹猛擊😊

總結(jié)

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

參考文章:

責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2023-04-21 09:35:50

2019-05-28 10:54:14

Linux運(yùn)行系統(tǒng)

2021-12-03 05:35:56

Windows 11操作系統(tǒng)微軟

2014-11-25 10:03:42

JavaScript

2012-12-17 13:51:22

Web前端JavaScriptJS

2009-06-24 15:00:39

Javascript代

2019-07-08 17:30:47

智能

2024-11-21 16:46:12

2020-11-05 10:05:49

螞蟻互聯(lián)網(wǎng)上市

2021-11-22 08:14:23

Linux Linux驅(qū)動(dòng)Linux 系統(tǒng)

2018-06-11 15:30:12

2018-10-10 14:02:39

前端JavaScript函數(shù)

2019-08-26 18:20:05

JavascriptWeb前端

2022-08-29 15:19:09

CSS煙花動(dòng)畫

2016-11-02 18:54:01

javascript

2013-03-18 16:09:27

JavaEEOpenfire

2009-07-06 19:29:37

云計(jì)算私有云服務(wù)器虛擬化

2017-03-07 15:54:13

華為

2012-03-22 10:33:33

思杰XenDesktop
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)