<menu id="ycqsw"></menu><nav id="ycqsw"><code id="ycqsw"></code></nav>
<dd id="ycqsw"><menu id="ycqsw"></menu></dd>
  • <nav id="ycqsw"></nav>
    <menu id="ycqsw"><strong id="ycqsw"></strong></menu>
    <xmp id="ycqsw"><nav id="ycqsw"></nav>
  • js刪除節點的方法(JSfor循環刪除指定節點)


    內存管理

    V8 內存限制

    限制大小

    64 位為 1.4GB,32 位為 0.7GB

    限制原因

    V8 之所以限制了內存的大小,表面上的原因是 V8 最初是作為瀏覽器的 JavaScript 引擎而設計,不太可能遇到大量內存的場景,而深層次的原因則是由于 V8 的垃圾回收機制的限制。由于 V8 需要保證 JavaScript 應用邏輯與垃圾回收器所看到的不一樣,V8 在執行垃圾回收時會阻塞 JavaScript 應用邏輯,直到垃圾回收結束再重新執行 JavaScript 應用邏輯,這種行為被稱為“全停頓”(stop-the-world)。若 V8 的堆內存為 1.5GB,V8 做一次小的垃圾回收需要 50ms 以上,做一次非增量式的垃圾回收甚至要 1 秒以上。這樣瀏覽器將在 1s 內失去對用戶的響應,造成假死現象。如果有動畫效果的話,動畫的展現也將顯著受到影響。

    V8 垃圾回收策略

    • 采用分代回收的思想
    • 內存分為新生代、老生代
    • 針對新、老生代采用不同算法來提升垃圾回收的效率

    新生代的對象為存活時間較短的對象,老生代中的對象為存活時間較長或常駐內存的對象。

    V8 新生代、老生代內存大小

    V8 引擎的新生代內存大小 32MB(64 位)、16MB(32 位),老生代內存大小為 1400MB(64 位)、700MB( 32 位)。

    新生代對象回收實現

    • 回收過程采用復制算法+標記整理
    • 新生代內存區被等分為兩個空間
    • 使用空間為 From,空閑空間為 To
    • 標記整理后將活動對象拷貝至 To
    • From 和 To 交換空間完成釋放
    JavaScript的內存和內存管理

    晉升

    將新生代對象移到老生代

    晉升條件

    • 一輪 GC 還存活的新生代需要晉升
    • 對象從 From 空間復制到 To 空間時,如果 To 空間已經被使用了超過 25%,那么這個對象直接被復制到老生代

    老生代對象回收實現

    • 主要采取標記清除、標記整理、增量標記算法
    • 首先使用標記清除完成垃圾空間的回收
    • 采用標記整理進行空間優化
    • 采用增量標記進行效率優化

    細節對比

    新生代區域,采用復制算法, 因此其每時每刻內部都有空閑空間的存在(為了完成 From 到 To 的對象復制),但是新生代區域空間較小(32M)且被一分為二,所以這種空間上的浪費也是比較微不足道的。

    老生代因其空間較大(1.4G),如果同樣采用一分為二的做法則對空間大小是比較浪費,且老生代空間較大,存放對對象也較多,如果進行復制算法,則其消耗對時間也會更大。也就是是否使用復制算法來進行垃圾回收,是一個時間 T 關于內存大小的關系,當內存大小較小時,使用復制算法消耗的時間是比較短的,而當內存較大時,采用復制算法對時間對消耗也就更大。

    V8 的優化

    增量標記

    由于全停頓會造成了瀏覽器一段時間無響應,所以 V8 使用了一種增量標記的方式,將完整的標記拆分成很多部分,每做完一部分就停下來,讓 JS 的應用邏輯執行一會,這樣垃圾回收與應用邏輯交替完成。經過增量標記的改進后,垃圾回收的最大停頓時間可以減少到原來的 1/6 左右

    JavaScript的內存和內存管理

    惰性清理

    由于標記完成后,所有的對象都已經被標記,不是死對象就是活對象,堆上多少空間格局已經確定。我們可以不必著急釋放那些死對象所占用的空間,而延遲清理過程的執行。垃圾回收器可以根據需要逐一清理死對象所占用的內存空間

    其他

    V8 后續還引入了增量式整理(incremental compaction),以及并行標記和并行清理,通過并行利用多核 CPU 來提升垃圾回收的性能

    監控內存

    內存問題的外在表現

    • 頁面出現延遲加載或經常性暫停: 可能存在頻繁當 GC 操作,存在一些代碼瞬間吃滿了內存。
    • 頁面出現持續性的糟糕性能: 程序為了達到最優的運行速度,向內存申請了一片較大的內存空間,但空間大小超過了設備所能提供的大小。
    • 頁面使用隨著時間延長越來越卡:可能存在內存泄漏。

    界定內存問題的標準

    • 內存泄漏:內存使用持續升高
    • 內存膨脹:在多數設備上都存在性能問題
    • 頻繁垃圾回收:通過內存變化時序圖進行分析

    監控內存方式

    任務管理器

    這里以 Google 瀏覽器為例,使用 Shift + Esc 喚起 Google 瀏覽器自帶的任務管理器

    • Memory(內存) 列表示原生內存。DOM 節點存儲在原生內存中。如果此值正在增大,則說明正在創建 DOM 節點。
    • JavaScript Memory(JavaScript 內存) 列表示 JS 堆。此列包含兩個值。您感興趣的值是實時數字(括號中的數字)。實時數字表示您的頁面上的可到達對象正在使用的內存量。如果此數字在增大,要么是正在創建新對象,要么是現有對象正在增長。

    模擬內存泄漏

    在任務管理器里可以看到 JavaScript 內存持續上升

    document.body.innerHTML = `<button id="add">add</button>`;
    document.getElementById('add').addEventListener('click', function (e) {
      simulateMemoryLeak();
    });
    let result = [];
    function simulateMemoryLeak() {
      setInterval(function () {
        result.push(new Array(1000000).join('x'));
        document.body.innerHTML = result;
      }, 100);
    }
    

    Timeline 記錄內存

    這里以 Google 瀏覽器為例,使用 F12 開啟調式,選擇 Performance,點擊 record(錄制),進行頁面操作,點擊 stop 結束錄制之后,開啟內存勾選,拖動截圖到指定時間段查看發生內存問題時候到頁面展示,并定位問題。同時可以查看對應出現紅點到執行腳本,定位問題代碼。

    JavaScript的內存和內存管理

    利用瀏覽器內存模塊,查找分離 dom

    這里以 Google 瀏覽器為例,在頁面上進行相關操作后,使用 F12 開啟調式,選擇 Memory,點擊 Take snapshot(拍照),在快照中查找 Detached HTMLElement,回到代碼中查找對應的分離 dom 存在的代碼,在相關操作代碼之后,對分離 dom 進行釋放,防止內存泄漏。

    只有頁面的 DOM 樹或 JavaScript 代碼不再引用 DOM 節點時,DOM 節點才會被作為垃圾進行回收。如果某個節點已從 DOM 樹移除,但某些 JavaScript 仍然引用它,我們稱此節點為“已分離”。已分離的 DOM 節點是內存泄漏的常見原因。

    模擬已分離 DOM 節點

    document.body.innerHTML = `<button id="add">add</button>`;
    document.getElementById('add').addEventListener('click', function (e) {
      create();
    });
    let detachedTree;
    function create() {
      let ul = document.createElement('ul');
      for (let i = 0; i < 10; i++) {
        let li = document.createElement('li');
        ul.appendChild(li);
      }
      detachedTree = ul;
    }
    
    JavaScript的內存和內存管理

    如何確定頻繁對垃圾回收

    • GC 工作時,程序是暫停的,頻繁/過長的 GC 會導致程序假死,用戶會感知到卡頓。
    • 查看 Timeline 中是否存在內存走向在短時間內頻繁上升下降的區域。瀏覽器任務管理器是否頻繁的增加減少。

    代碼優化

    jsPerf(JavaScript 性能測試)

    基于 Benchmark.js

    慎用全局變量

    • 全局變量定義在全局執行的上下文,是所有作用域鏈的頂端
    • 全局執行上下文一直存在于上下文執行棧,直到程序退出
    • 如果某個局部作用域出現了同名變量則會屏蔽或者污染全局作用域
    • 全局變量的執行速度,訪問速度要低于局部變量,因此對于一些需要經常訪問的全局變量可以在局部作用域中進行緩存
    JavaScript的內存和內存管理

    上圖可以看出,test2 的性能要比 test1 的性能要好,從而得知,全局變量的執行速度,訪問速度要低于局部變量

    避免全局查找

    JavaScript的內存和內存管理

    上圖可以看出,test2 的性能要比 test1 的性能要好,從而得知,緩存全局變量后使用可以提升性能

    通過原型對象添加附加方法提高性能

    JavaScript的內存和內存管理

    上圖可以看出,test2 的性能要比 test1 的性能要好,從而得知,通過原型對象添加方法與直接在對象上添加成員方法相比,原型對象上的屬性訪問速度較快。

    避開閉包陷阱

    閉包特點

    • 外部具有指向內部的引用
    • 在“外”部作用域訪問“內”部作用域的數據
    function foo() {
      let name = 'heath';
      function fn() {
        console.log(name);
      }
      return fn;
    }
    let a = foo();
    a();
    

    閉包使用不當很容易出現內存泄漏

    function f5() {
      // el 引用了全局變量document,假設btn節點被刪除后,因為這里被引用著,所以這里不會被垃圾回收,導致內存泄漏
      let el = document.getElementById('btn');
      el.onclick = function (e) {
        console.log(e.id);
      };
    }
    f5();
    function f6() {
      // el 引用了全局變量document,假設btn節點被刪除后,因為這里被引用著,所以這里不會被垃圾回收,導致內存泄漏
      let el = document.getElementById('btn');
      el.onclick = function (e) {
        console.log(e.id);
      };
      el = null; // 我們這里手動將el內存釋放,從而當btn節點被刪除后,可以被垃圾回收
    }
    f6();
    

    避免屬性訪問方法使用

    JavaScript 中的面向對象

    • JS 不需屬性的訪問方法,所有屬性都是外部可見的
    • 使用屬性訪問方法只會增加一層重定義,沒有訪問的控制力
    JavaScript的內存和內存管理

    上圖可以看出,test2 的性能要比 test1 的性能要好不少,從而得知,直接訪問屬性,會比通過方法訪問屬性速度來的快。

    遍歷速度

    JavaScript的內存和內存管理

    上圖可以看出,loop 遍歷速度 forEach > 優化 for > for of > for > for in

    版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 舉報,一經查實,本站將立刻刪除。

    發表評論

    登錄后才能評論
    国产精品区一区二区免费