js晉級篇——前端記憶體泄漏探討

来源:http://www.cnblogs.com/chuaWeb/archive/2016/02/17/5196330.html
-Advertisement-
Play Games

1.IE7/8 DOM對象或者ActiveX對象迴圈引用導致記憶體泄漏 迴圈引用分為兩種: 第一種:多個對象迴圈引用 var a=new Object; var b=new Object; a.r=b; b.r=a; 第二種:迴圈引用自己 var a=new Object; a.r=a; 對於ECMA


1.IE7/8 DOM對象或者ActiveX對象迴圈引用導致記憶體泄漏


  迴圈引用分為兩種:

  第一種:多個對象迴圈引用

var a=new Object;
var b=new Object;
a.r=b;
b.r=a;

  第二種:迴圈引用自己

var a=new Object;
a.r=a;

  對於ECMAScript 對象而言,只要沒有其他對象引用對象 a、b,也就是說它們只是相互之間的引用,那麼仍然會被垃圾收集系統識別並處理。

  但是,在 IE7、IE8 中,如果迴圈引用中的任何對象是 DOM 節點或者 ActiveX 對象,比如var a = document.getElementById("#a"),垃圾收集系統則不會發現它們之間的迴圈關係,因為IE的DOM回收機制和JS回收機制不是同一個。js回收機制分兩種:標記清除引用計數引用計數對迴圈引用的垃圾回收會出現記憶體泄漏,而IE的DOM回收機制便是採用引用計數的。IE9+並不存在迴圈引用導致Dom記憶體泄露問題,可能是微軟做了優化,或者Dom的回收方式已經改變。

下麵摘自小蘋果的跟我學習javascript的垃圾回收機制與記憶體管理

二、標記清除
js中最常用的垃圾回收方式就是標記清除。當變數進入環境時,例如,在函數中聲明一個變數,就將這個變數標記為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變數所占用的記憶體,因為只要執行流進入相應的環境,就可能會用到它們。而當變數離開環境時,則將其標記為“離開環境”。
function test(){
 var a = 10 ; //被標記 ,進入環境 
 var b = 20 ; //被標記 ,進入環境
}
test(); //執行完畢 之後 a、b又被標離開環境,被回收。
  垃圾回收器在運行的時候會給存儲在記憶體中的所有變數都加上標記(當然,可以使用任何標記方式)。然後,它會去掉環境中的變數以及被環境中的變數引用的變數的標記(閉包)。而在此之後再被加上標記的變數將被視為準備刪除的變數,原因是環境中的變數已經無法訪問到這些變數了。最後,垃圾回收器完成記憶體清除工作,銷毀那些帶標記的值並回收它們所占用的記憶體空間。
  到目前為止,IE、Firefox、Opera、Chrome、Safari的js實現使用的都是標記清除的垃圾回收策略或類似的策略,只不過垃圾收集的時間間隔互不相同。
三、引用計數   引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明瞭一個變數並將一個引用類型值賦給該變數時,則這個值的引用次數就是1。如果同一個值又被賦給另一個變數,則該值的引用次數加1。相反,如果包含對這個值引用的變數又取得了另外一個值,則這個值的引用次數減1。當這個值的引用次數變成0時,則說明沒有辦法再訪問這個值了,因而就可以將其占用的記憶體空間回收回來。這樣,當垃圾回收器下次再運行時,它就會釋放那些引用次數為0的值所占用的記憶體。
function test(){ var a = {} ; //a的引用次數為0 var b = a ; //a的引用次數加1,為1 var c =a; //a的引用次數再加1,為2 var b ={}; //a的引用次數減1,為1 }   Netscape Navigator3是最早使用引用計數策略的瀏覽器,但很快它就遇到一個嚴重的問題:迴圈引用。迴圈引用指的是對象A中包含一個指向對象B的指針,而對象B中也包含一個指向對象A的引用。 function fn() { var a = {}; var b = {}; a.pro = b; b.pro = a; } fn();   以上代碼a和b的引用次數都是2,fn()執行完畢後,兩個對象都已經離開環境,在標記清除方式下是沒有問題的,但是在引用計數策略下,因為a和b的引用次數不為0,所以不會被垃圾回收器回收記憶體,如果fn函數被大量調用,就會造成記憶體泄露。在IE7與IE8上,記憶體直線上升。   我們知道,IE中有一部分對象並不是原生js對象。例如,其記憶體泄露DOM和BOM中的對象就是使用C++以COM對象的形式實現的,而COM對象的垃圾回收機制採用的就是引用計數策略。因此,即使IE的js引擎採用標記清除策略來實現,但js訪問的COM對象依然是基於引用計數策略的。換句話說,只要在IE中涉及COM對象,就會存在迴圈引用的問題。 var element = document.getElementById("some_element"); var myObject = new Object(); myObject.e = element; element.o = myObject;   這個例子在一個DOM元素(element)與一個原生js對象(myObject)之間創建了迴圈引用。其中,變數myObject有一個名為element的屬性指向element對象;而變數element也有一個屬性名為o回指myObject。由於存在這個迴圈引用,即使例子中的DOM從頁面中移除,它也永遠不會被回收。   看上面的例子,有同學回覺得太弱了,誰會做這樣無聊的事情,其實我們是不是就在做 window.onload=function outerFunction(){ var obj = document.getElementById("element"); obj.onclick=function innerFunction(){}; };   這段代碼看起來沒什麼問題,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法會引用外部環境中的變數,自然也包括obj,是不是很隱蔽啊。   解決辦法   最簡單的方式就是自己手工解除迴圈引用,比如剛纔的函數可以這樣 myObject.element = null; element.o = null; window.onload=function outerFunction(){ var obj = document.getElementById("element"); obj.onclick=function innerFunction(){}; obj=null; };   將變數設置為null意味著切斷變數與它此前引用的值之間的連接。當垃圾回收器下次運行時,就會刪除這些值並回收它們占用的記憶體。   要註意的是,IE9+並不存在迴圈引用導致Dom記憶體泄露問題,可能是微軟做了優化,或者Dom的回收方式已經改變

  在上面描述的迴圈引用例子

window.onload=function outerFunction(){
var obj = document.getElementById("element");
  obj.onclick=function innerFunction(){};
};

   還可以理解成閉包迴圈引用導致的記憶體泄漏。怎麼理解?

  首先obj是外部的一個對象, obj.onclick定義的這個函數隱式的調用到了obj這個對象(obj.onclick函數中的this就是對象obj)。然後我們需要知道obj.onclick實際上是一個outerFunction外部的函數,為什麼?DOM監聽事件不可能是局部作用域的,是全局作用域的,明白了吧。所以DOM觸發這個事件相當於是在函數outerFunction外部調用了obj.click(),而事件內部使用了outerFunction的變數obj,這就形成了一個閉包。IE7/IE8 DOM的引用計數永遠無法回收這個DOM對象。無論如何,這都是DOM迴圈引用導致的記憶體泄漏,普通閉包是不會導致記憶體泄漏的。

  改成如下結構

window.onload=function outerFunction(){
  var obj = document.getElementById("element");
  $(obj).click(function innerFunction(){});
};

   jQuery綁定事件最終都沒有直接綁定到DOM對象上,而是使用jQuery緩存來綁定的。詳見jQuery事件體繫結構

  即使此時仍然會創建一個閉包,並且也會導致同前面一樣的迴圈,但這裡的代碼卻不會使 IE 發生記憶體泄漏。由於jQuery考慮到了記憶體泄漏的潛在危害,所以它會手動釋放自己指定的所有事件處理程式(jQuery源代碼$.fn.remove函數中有對節點的緩存釋放的處理)。只要堅持使用jQuery的事件綁定方法,就無需為這種特定的常見原因導致的記憶體泄漏而擔心。

  但是,這並不意味著我們完全脫離了險境。當對DOM元素進行其他操作時,仍然要處處留心。只要是將JavaScript對象指定給DOM元素,就可能在舊版本IE中導致記憶體泄漏。jQuery只是有助於減少發生這種情況的可能性。

  有鑒於此,jQuery為我們提供了另一個避免這種泄漏的工具。用.data()方法,將信息附加到DOM元素。由於這裡的數據並非直接保存在擴展屬性中(jQuery使用一個內部對象並通過它創建的ID來保存這裡所說的數據),因此永遠也不會構成引用迴圈,從而有效迴避了記憶體泄漏問題。這種方式也就是jQuery事件綁定使用的方式。

  

2.基礎的DOM泄漏


當原有的DOM被移除時,子結點引用沒有被移除則無法回收

var select = document.querySelector;
var treeRef = select('#tree');

var leafRef = select('#leaf');   //在COM樹中leafRef是treeFre的一個子結點

select('body').removeChild(treeRef);//#tree不能被回收入,因為treeRef還在

   解決方法:

treeRef = null;//tree還不能被回收,因為葉子結果leafRef還在
leafRef = null;//現在#tree可以被釋放了

  

DOM 插入順序導致記憶體泄漏

  當 動態創建的2 個不同範圍的 DOM 對象附加到一起的時候,一個臨時的對象會被創建。這個 DOM 對象改變範圍到 document 時,那個臨時對象就沒用了,這個臨時對象沒有被回收將導致記憶體泄漏。如果我們一一將這兩個DOM添加到原有的 本省存在的DOM 對象上就不會產生中間臨時對象。詳見理解和解決IE記憶體泄漏頁面交叉泄露 Cross-Page Leaks。

 

3.timer定時器泄漏



var
val = 0; for (var i = 0; i < 90000; i++) { var buggyObject = { callAgain: function() { var ref = this; val = setTimeout(function() { ref.callAgain(); }, 90000); } } buggyObject.callAgain();

   這個時候你無法回收buggyObject

//雖然你想回收但是timer還在
buggyObject = null;

    解決辦法,先停止timer然後再回收

//解決方法,先停止定時器
clearTimeout(val);
buggyObject = null;

  

推薦記憶體泄漏文章:

理解和解決IE記憶體泄漏(中文翻譯):http://www.tuicool.com/articles/2AZ3y2

理解和解決IE記憶體泄漏(英文原版):https://msdn.microsoft.com/en-us/library/bb250448.aspx

js記憶體泄露的幾種情況:http://blog.csdn.net/li2274221/article/details/25217297

 

  如果覺得本文不錯,請點擊右下方【推薦】!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、路由是程式的方法和URL的一一映射。 在配置文件里,把經常訪問的路由放在前面,可以提高路由匹配的效率。 2、路由匹配的兩種方式 Annotation 允許在方法的上面用註釋定義方法運行狀態的功能 class UserController extends Controller{ /** * @Ro
  • lazy概念:要用到的時候,再去載入,對於關聯的集合來說,只有當訪問到的時候,才去載入它所關聯的集合,比如一個user對應很多許可權,只有當user.getRights()的時候,才發出select right的語句,在訪問到rights之前,rights是一個PersisitSet對於實體類來說,只
  • 1 <?php 2 // 正確地顯示覆數 3 if(!function_exists('_plurals_format')) 4 { 5 /** 6 * 正確的使用複數 7 * @access public 8 * @author zhaoyingnan 2016-02-17 11:53 9 * @
  • pyextend - python extend lib accepts(exception=TypeError, **types) 參數: exception: 檢查失敗時的拋出異常類型 **types: 待檢查的k-v參數 **types參數支持 a=int : 待測函數參數a必須為 int 類
  • 抽象類 1 package com.shejimoshi.structural.Adapter; 2 3 4 /** 5 * 功能:適配器模式 6 * 將一個類的介面轉換成客戶希望的另外一個介面。adapter模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作 7 * 適用性:你想使用一個
  • MVVM框架的性能,其實就取決於幾個因素: 監控的個數 數據變更檢測與綁定的方式 索引的性能 數據的大小 數據的結構 我們要優化Angular項目的性能,也需要從這幾個方面入手。 1. 減少監控值的個數 監控值的個數怎麼減少呢? 考慮極端情況,在不引入Angular的時候,監控的個數是為0的,每當我
  • 重要程度:★★★★★ 一、什麼是外觀模式 在子系統中的介面之上定義一個更高層次的介面,方便使用子系統中的介面; 二、補充說明 缺點:不符合開閉原則,修改子系統的代碼會影響高層介面代碼; 優點:引入外觀類可以降低系統的複雜度,提高了客戶端使用的便捷性; 優點:客戶類與子系統解耦; 三、角色 子系統角色
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...