<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JavaScript進階-常見內存泄露及如何避免

          共 3864字,需瀏覽 8分鐘

           ·

          2020-10-17 20:46

          前言

          這一章節(jié)給大家介紹的知識點相對比較簡單, 但是卻是非常重要的. 而且也是在面試過程中經(jīng)常會被問到的一部分內容.

          通過此次閱讀你可以學習到:

          • 4種常見的內存泄露
          • 內存泄露的識別方法

          4種常見的內存泄露

          其實在實際開發(fā)中, 我們很容易不經(jīng)意的就寫出內存泄露的代碼, 比如以下幾種情況可能都是你遇到過的.

          一、意外的全局變量

          未聲明的變量

          當我們在一個函數(shù)中給一個變量賦值但是卻沒有聲明它時:

          function fn () {
          a = "Actually, I'm a global variable"
          }

          此時變量a相當于是window對象下的一個變量:

          function fn () {
          window.a = "Actually, I'm a global variable"
          }

          而之前我們已經(jīng)說了全局變量是很難被垃圾回收器回收的, 所以要是有這種意外的全局變量應該要避免.

          使用this創(chuàng)建的變量

          還有一種情況是這樣的:

          function fn () {
          this.a = "Actually, I'm a global variable"
          }
          fn();

          我們知道, 這里this的指向是window, 因此此時創(chuàng)建的a變量也會被掛載到window對象下.

          避免此情況的解決辦法是在 JavaScript 文件頭部或者函數(shù)的頂部加上 'use strict', 開啟嚴格模式, 使得this的指向為undefined, 這樣就可以避免了.

          ?

          當然如果你必須使用全局變量存儲大量數(shù)據(jù)時,確保用完以后把它設置為 null 或者重新定義。

          二、被遺忘的計時器或回調函數(shù)

          定時器引起

          當我們在代碼中使用定時器也有可能會造成內存泄露:

          var serverData = loadData()
          setInterval(function() {
          var renderer = document.getElementById('renderer')
          if(renderer) {
          renderer.innerHTML = JSON.stringify(serverData)
          }
          }, 5000)

          上面的例子?中, 節(jié)點renderer引用了serverData.在節(jié)點renderer或者數(shù)據(jù)不再需要時,定時器依舊指向這些數(shù)據(jù)。所以哪怕當renderer節(jié)點被移除后,interval 仍舊存活并且垃圾回收器沒辦法回收,它的依賴也沒辦法被回收,除非終止定時器。

          對象觀察者

          還有一個就是關于觀察者模式的案例:

          var btn = document.getElementById('btn');
          function onClick (element) {
          element.innerHTMl = "I'm innerHTML"
          }
          btn.addEventListener('click', onClick);

          對于上面觀察者的例子,一旦它們不再需要(或者關聯(lián)的對象變成不可達),明確地移除它們非常重要。老的 IE 6 是無法處理循環(huán)引用的。因為老版本的 IE 是無法檢測 DOM 節(jié)點與 JavaScript 代碼之間的循環(huán)引用,會導致內存泄漏。

          但是,現(xiàn)代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收算法(標記清除),已經(jīng)可以正確檢測和處理循環(huán)引用了。即回收節(jié)點內存時,不必非要調用 removeEventListener 了。

          三、脫離DOM的引用

          這種造成內存泄露的原因簡單來說就是:

          如果把DOM 存成字典(JSON 鍵值對)或者數(shù)組,此時,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另一個在字典中。那么將來需要把兩個引用都清除。

          比如下面這個例子:

          // 在對象中引用DOM
          var elements = {
          btn: document.getElementById('btn')
          }

          function doSomeThing () {
          elements.btn.click();
          }

          function removeBtn () {
          // 將body中的btn移除, 也就是移除 DOM樹中的btn
          document.body.removeChild(document.getElementById('button'));
          // 但是此時全局變量elements還是保留了對btn的引用, btn還是存在于內存中,不能被GC回收
          }

          上面?這種情況, 可以手動將引用給清除: elements.btn = null.

          四、閉包

          還有一種情況就是我們之前提到過的閉包. 也就是局部變量銷毀時, 閉包的這種情況.

          首先讓我們明確一點:閉包的關鍵就是匿名函數(shù)能夠訪問父級作用域中的變量.

          來看一個簡單的例子?:

          function fn () {
          var a = "I'm a";
          return function () {
          console.log(a);
          };
          }

          因為變量afn()函數(shù)內的匿名函數(shù)所引用, 因此這種變量是不會被回收的.

          那就有人問了, 即使這樣會造成什么不妥嗎? 在上面?這個案例中當然看不出有什么. 若是將情況變得復雜一些呢?

          var globalVar = null; // 全局變量
          var fn = function () {
          var originVal = globalVar; // 局部變量
          var unused = function () { // 未使用的函數(shù)
          if (originVal) {
          console.log('call')
          }
          }
          globalVar = {
          longStr: new Array(1000000).join('*'),
          someThing: function () {
          console.log('someThing')
          }
          }
          }
          setInterval(fn, 100);

          先請你花上一分鐘看看上面的案例, 你會發(fā)現(xiàn):

          1. 每次調用fn函數(shù)的時候都會產生一個新的對象originVal;
          2. 變量unused是一個引用了originVal的閉包;
          3. unused雖然未被使用, 但是它引用的originVal迫使它留在內存中, 并不會被回收.

          解決辦法是: 可以在fn的最底部, 將originVal設置成null.

          內存泄露的識別方法

          上面?介紹了這么多種可能會造成內存泄露的情況, 那么有沒有什么實際的辦法讓我們看到內存泄露的表現(xiàn)呢?

          當然是有的. 現(xiàn)在常用的是以下2種方式:

          • Chrome瀏覽器的控制臺PerformanceMemory
          • Node提供的process.memoryUsage方法

          Chrome瀏覽器的控制臺PerformanceMemory

          Chrome 瀏覽器查看內存占用,按照以下步驟操作。

          1. 在網(wǎng)頁上右鍵, 點擊“檢查”打開控制臺(Mac快捷鍵option+command+i);
          2. 選擇Performance面板(很多教材中用的是Timeline面板, 不知道是不是版本的原因);
          3. 勾選Memory, 然后點擊左上角的小黑點Record開始錄制;
          4. 點擊彈窗中的Stop結束錄制, 面板上就會顯示這段時間的內存占用情況。

          如果內存的使用情況一直在做增量, 那么就是內存泄露了:

          或者你可以使用我在《記錄一次定時器及閉包問題造成的內存泄漏》中的方法進行檢查.

          Node提供的process.memoryUsage方法

          另一個就是Node提供的process.memoryUsage方法, 這一塊我用的不是很多, 這里就貼上教材:

          console.log(process.memoryUsage());
          // { rss: 27709440,
          // heapTotal: 5685248,
          // heapUsed: 3449392,
          // external: 8772 }

          process.memoryUsage返回一個對象,包含了 Node 進程的內存占用信息。該對象包含四個字段,單位是字節(jié),含義如下:

          rss(resident set size):所有內存占用,包括指令區(qū)和堆棧。
          heapTotal:"堆"占用的內存,包括用到的和沒用到的。
          heapUsed:用到的堆的部分。
          external:V8 引擎內部的 C++ 對象占用的內存。

          判斷內存泄露, 是看heapUsed字段.

          總結

          總的來說, 常見的內存泄露包括:

          • 意外的全局變量
          • 被遺忘的定時器或回調函數(shù)
          • 脫離DOM的引用
          • 閉包中重復創(chuàng)建的變量

          如何避免內存泄露:

          • 注意程序邏輯,避免“死循環(huán)”之類的
          • 減少不必要的全局變量,或者生命周期較長的對象,及時對無用的數(shù)據(jù)進行垃圾回收
          • 避免創(chuàng)建過多的對象 原則:不用了的東西要及時歸還

          后語

          如果你喜歡探討技術,或者對本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。魚頭的微信號是:krisChans95 也可以掃碼關注公眾號,訂閱更多精彩內容。


          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天堂网地址 | 天天撸日日干 | 欧美肏逼网站 | 大香焦久久久 | 狠狠擼成人AV |