<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] # 簡介 從本質上說,內存泄漏可以定義為:不再被應用程序所需要的內存,出于某種原因,它不會返回到操作系統或空閑內存池中。 <br> # 四種常見的內存泄漏 ## 全局變量 JavaScript以一種有趣的方式處理未聲明的變量: 對于未聲明的變量,會在全局范圍中創建一個新的變量來對其進行引用。在瀏覽器中,全局對象是window。例如: ~~~ function foo(arg) { bar = "some text"; } ~~~ 等價于: ~~~ function foo(arg) { window.bar = "some text"; } ~~~ 如果 bar 在 foo 函數的作用域內對一個變量進行引用,卻忘記使用 var 來聲明它,那么將創建一個意想不到的全局變量。在這個例子中,遺漏一個簡單的字符串不會造成太大的危害,但這肯定會很糟。 <br> 創建一個意料之外的全局變量的另一種方法是使用this: ~~~ function foo() { this.var1 = "potential accidental global"; } // Foo自己調用,它指向全局對象(window),而不是未定義。 foo(); ~~~ <br> > 可以在JavaScript文件的開頭通過添加“use strict”來避免這一切,它將開啟一個更嚴格的JavaScript解析模式,以防止意外創建全局變量。 <br> 盡管我們討論的是未知的全局變量,但仍然有很多代碼充斥著顯式的全局變量。根據定義,這些是不可收集的(除非被指定為空或重新分配)。用于臨時存儲和處理大量信息的全局變量特別令人擔憂。如果你必須使用一個全局變量來存儲大量數據,那么請確保將其指定為null,或者在完成后將其重新賦值。 <br> ## 被遺忘的定時器和回調 以`setInterval`為例,因為它在JavaScript中經常使用。 ~~~ var serverData = loadData(); setInterval(function() { var renderer = document.getElementById('renderer'); if(renderer) { renderer.innerHTML = JSON.stringify(serverData); } }, 5000); //每五秒會執行一次 ~~~ <br> 上面的代碼片段演示了使用定時器時引用不再需要的節點或數據。 <br> renderer 表示的對象可能會在未來的某個時間點被刪除,從而導致內部處理程序中的一整塊代碼都變得不再需要。但是,由于定時器仍然是活動的,所以,處理程序不能被收集,并且其依賴項也無法被收集。這意味著,存儲著大量數據的 serverData 也不能被收集。 <br> 在使用觀察者時,您需要確保在使用完它們之后進行顯式調用來刪除它們(要么不再需要觀察者,要么對象將變得不可訪問)。 <br> 作為開發者時,需要確保在完成它們之后進行顯式刪除它們(或者對象將無法訪問)。 <br> 在過去,一些瀏覽器無法處理這些情況(很好的IE6)。幸運的是,現在大多數現代瀏覽器會為幫你完成這項工作:一旦觀察到的對象變得不可訪問,即使忘記刪除偵聽器,它們也會自動收集觀察者處理程序。然而,我們還是應該在對象被處理之前顯式地刪除這些觀察者。例如: ![](https://box.kancloud.cn/42b5d068292f712e35ead022716fd791_800x392.png) <br> 如今,現在的瀏覽器(包括IE和Edge)使用現代的垃圾回收算法,可以立即發現并處理這些循環引用。換句話說,在一個節點刪除之前也不是必須要調用removeEventListener。 <br> 一些框架或庫,比如JQuery,會在處置節點之前自動刪除監聽器(在使用它們特定的API的時候)。這是由庫內部的機制實現的,能夠確保不發生內存泄漏,即使在有問題的瀏覽器下運行也能這樣,比如IE 6。 <br> ## 閉包 閉包是javascript開發的一個關鍵方面,一個內部函數使用了外部(封閉)函數的變量。由于JavaScript運行的細節,它可能以下面的方式造成內存泄漏: ![](https://box.kancloud.cn/df281f03c093b7f5a75330ae81e803fb_800x487.png) <br> 這段代碼做了一件事:每次調用`replaceThing`的時候,`theThing`都會得到一個包含一個大數組和一個新閉包(someMethod)的新對象。同時,變量`unuse`d指向一個引用了``originalThing`的閉包。 <br> 是不是有點困惑了? 重要的是,一旦具有相同父作用域的多個閉包的作用域被創建,則這個作用域就可以被共享。 <br> 在這種情況下,為閉包`someMethod`而創建的作用域可以被`unused`共享的。`unused`內部存在一個對`originalThing`的引用。即使`unused`從未使用過,`someMethod`也可以在`replaceThing`的作用域之外(例如在全局范圍內)通過`theThing`來被調用。 <br> 由于`someMethod`共享了`unused`閉包的作用域,那么`unused`引用包含的`originalThing`會迫使它保持活動狀態(兩個閉包之間的整個共享作用域)。這阻止了它被收集。 <br> 當這段代碼重復運行時,可以觀察到內存使用在穩定增長,當`GC`運行后,內存使用也不會變小。從本質上說,在運行過程中創建了一個閉包鏈表(它的根是以變量`theThing`的形式存在),并且每個閉包的作用域都間接引用了了一個大數組,這造成了相當大的內存泄漏。 <br> ## 脫離DOM的引用 有時,將DOM節點存儲在數據結構中可能會很有用。假設你希望快速地更新表中的幾行內容,那么你可以在一個字典或數組中保存每個DOM行的引用。這樣,同一個DOM元素就存在兩個引用:一個在DOM樹中,另一個則在字典中。如果在將來的某個時候你決定刪除這些行,那么你需要將這兩個引用都設置為不可訪問。 ![](https://box.kancloud.cn/6d82297a53a37105ab994e8b14ad0126_800x395.png) <br> 在引用 DOM 樹中的內部節點或葉節點時,還需要考慮另外一個問題。如果在代碼中保留對表單元格的引用(標記),并決定從 DOM 中刪除表,同時保留對該特定單元格的引用,那么可能會出現內存泄漏。 <br> 你可能認為垃圾收集器將釋放除該單元格之外的所有內容。然而,事實并非如此,由于單元格是表的一個子節點,而子節點保存對父節點的引用,所以對表單元格的這個引用將使**整個表保持在內存中**,所以在移除有被引用的節點時候要移除其子節點。 <br> <br> # 內存泄漏的識別方法 怎樣可以觀察到內存泄漏呢? [經驗法則](https://www.toptal.com/nodejs/debugging-memory-leaks-node-js-applications)是,如果連續五次垃圾回收之后,內存占用一次比一次大,就有內存泄漏。這就要求實時查看內存占用。 <br> ## 瀏覽器 Chrome 瀏覽器查看內存占用,按照以下步驟操作 1. 打開開發者工具,選擇 Performance 面板 2. 勾選 Memory 3. 點擊左上角的錄制按鈕。 4. 在頁面上進行各種操作,模擬用戶的使用情況。 5. 一段時間后,點擊對話框的 stop 按鈕,面板上就會顯示這段時間的內存占用情況。 <br> 如果內存占用基本平穩,接近水平,就說明不存在內存泄漏。 <br> ![](https://box.kancloud.cn/beace1717bf9da892dc04ecd9583e45d_600x307.png) <br> 反之,就是內存泄漏了。 <br> ![](https://box.kancloud.cn/44fd71c9d0297e10edd4099a8f13aa7f_599x307.png) <br> ## 命令行 命令行可以使用 Node 提供的[`process.memoryUsage`](https://nodejs.org/api/process.html#process_process_memoryusage)方法。 ~~~javascript console.log(process.memoryUsage()); // { rss: 27709440, // heapTotal: 5685248, // heapUsed: 3449392, // external: 8772 } ~~~ <br> `process.memoryUsage`返回一個對象,包含了 Node 進程的內存占用信息。該對象包含四個字段,單位是字節,[含義](http://stackoverflow.com/questions/12023359/what-do-the-return-values-of-node-js-process-memoryusage-stand-for)如下。 ![](https://box.kancloud.cn/03990390389f48dd7f3753a7be340841_550x543.png) <br> * rss(resident set size):所有內存占用,包括指令區和堆棧。 * heapTotal:"堆"占用的內存,包括用到的和沒用到的。 * heapUsed:用到的堆的部分。 * external: V8 引擎內部的 C++ 對象占用的內存。 <br> 判斷內存泄漏,以heapUsed字段為準。 <br> <br> # WeakMap 前面說過,及時清除引用非常重要。但是,你不可能記得那么多,有時候一疏忽就忘了,所以才有那么多內存泄漏。 <br> 最好能有一種方法,在新建引用的時候就聲明,哪些引用必須手動清除,哪些引用可以忽略不計,當其他引用消失以后,垃圾回收機制就可以釋放內存。這樣就能大大減輕程序員的負擔,你只要清除主要引用就可以了。 <br> ES6 考慮到了這一點,推出了兩種新的數據結構:[WeakSet](http://es6.ruanyifeng.com/#docs/set-map#WeakSet)和[WeakMap](http://es6.ruanyifeng.com/#docs/set-map#WeakMap)。它們對于值的引用都是不計入垃圾回收機制的,所以名字里面才會有一個"Weak",表示這是弱引用。 <br> 下面以 WeakMap 為例,看看它是怎么解決內存泄漏的。 ~~~ const wm = new WeakMap(); const element = document.getElementById('example'); wm.set(element, 'some information'); wm.get(element) // "some information" ~~~ <br> 上面代碼中,先新建一個 Weakmap 實例。然后,將一個 DOM 節點作為鍵名存入該實例,并將一些附加信息作為鍵值,一起存放在 WeakMap 里面。這時,WeakMap 里面對`element`的引用就是弱引用,不會被計入垃圾回收機制。 <br> 也就是說,DOM 節點對象的引用計數是`1`,而不是`2`。這時,一旦消除對該節點的引用,它占用的內存就會被垃圾回收機制釋放。Weakmap 保存的這個鍵值對,也會自動消失。 <br> 基本上,如果你要往對象上添加數據,又不想干擾垃圾回收機制,就可以使用 WeakMap。 <br> <br> ## WeakMap 示例 WeakMap 的例子很難演示,因為無法觀察它里面的引用會自動消失。此時,其他引用都解除了,已經沒有引用指向 WeakMap 的鍵名了,導致無法證實那個鍵名是不是存在。 <br> 首先,打開 Node 命令行。 ~~~ node --expose-gc ~~~ <br> 上面代碼中,`--expose-gc`參數表示允許手動執行垃圾回收機制。 <br> 然后,執行下面的代碼。 ~~~ // 手動執行一次垃圾回收,保證獲取的內存使用狀態準確 > global.gc(); undefined // 查看內存占用的初始狀態,heapUsed 為 4M 左右 > process.memoryUsage(); { rss: 21106688, heapTotal: 7376896, heapUsed: 4153936, external: 9059 } > let wm = new WeakMap(); undefined > let b = new Object(); undefined > global.gc(); undefined // 此時,heapUsed 仍然為 4M 左右 > process.memoryUsage(); { rss: 20537344, heapTotal: 9474048, heapUsed: 3967272, external: 8993 } // 在 WeakMap 中添加一個鍵值對, // 鍵名為對象 b,鍵值為一個 5*1024*1024 的數組 > wm.set(b, new Array(5*1024*1024)); WeakMap {} // 手動執行一次垃圾回收 > global.gc(); undefined // 此時,heapUsed 為 45M 左右 > process.memoryUsage(); { rss: 62652416, heapTotal: 51437568, heapUsed: 45911664, external: 8951 } // 解除對象 b 的引用 > b = null; null // 再次執行垃圾回收 > global.gc(); undefined // 解除 b 的引用以后,heapUsed 變回 4M 左右 // 說明 WeakMap 中的那個長度為 5*1024*1024 的數組被銷毀了 > process.memoryUsage(); { rss: 20639744, heapTotal: 8425472, heapUsed: 3979792, external: 8956 } ~~~ <br> 上面代碼中,只要外部的引用消失,WeakMap 內部的引用,就會自動被垃圾回收清除。由此可見,有了它的幫助,解決內存泄漏就會簡單很多。 <br> <br> # 參考資料 [JavaScript如何工作:內存管理+如何處理4個常見的內存泄漏](https://segmentfault.com/a/1190000017392370) [JavaScript 內存泄漏教程](http://www.ruanyifeng.com/blog/2017/04/memory-leak.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看