亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

一篇文章弄懂javascript內(nèi)存泄漏

 更新時間:2021年05月13日 11:13:08   作者:hugo233  
js的垃圾回收機制就是為了防止內(nèi)存泄漏的,這篇文章主要給大家介紹了如何通過一篇文章弄懂javascript內(nèi)存泄漏的相關資料,需要的朋友可以參考下

1、什么是內(nèi)存泄漏

在了解什么是內(nèi)存泄漏之前, 我們應該要對內(nèi)存是什么有個概念, 隨機存取存儲器(英語:Random Access Memory,縮寫:RAM)是與 CPU 直接交換數(shù)據(jù)的內(nèi)部存儲器。它可以隨時讀寫, 而且速度很快,通常作為操作系統(tǒng)或其他正在運行中的程序的臨時資料存儲介質。

什么是內(nèi)存泄漏? :

程序不再需要使用的內(nèi)存, 但是又沒有及時釋放, 就叫做內(nèi)存泄漏!

然后在理解泄漏之前, 我們的了解下內(nèi)存的管理, 在一些底層語言中, 如C語言, 內(nèi)存是需要開發(fā)者自己分配和釋放的, 通過malloc、free等函數(shù)進行內(nèi)存管理.

<pre class="custom">`char * buffer;
// 使用malloc申請內(nèi)存空間
buffer = (char*) malloc (66);
// do something ...
// 不需要時, 釋放掉內(nèi)存空間引用
free(buffer);

上面這一小段代碼就是C語言中關于內(nèi)存的申請和釋放, 但是眾所周知在javascript中是不需要開發(fā)者手動管理內(nèi)存的, 在Chrome中有V8引擎幫我們自動進行內(nèi)存的分配和回收, 這就是垃圾回收機制, 但這并不代表我們在編寫代碼是不需要考慮內(nèi)存的事情, 因為V8垃圾回收機制是有特定的規(guī)則的, 了解這些規(guī)則可以讓我們避免寫出內(nèi)存泄漏的爛代碼.

那么先了解下javascript垃圾回收機制常見的兩種方法吧:

  • 引用計數(shù)算法
  • 標記清除算法

引用計數(shù)法

IE使用的是引用計數(shù)算法, 這種方法無法解決循環(huán)引用的垃圾回收問題, 容易造成內(nèi)存泄漏

那么什么是引用計數(shù)算法呢? 什么又是循環(huán)引用問題呢?

所謂引用計數(shù)即, 我們有一個變量每次被引用GC機制就會給這個變量計數(shù)加一, 當引用減少就計數(shù)減一, 如果計數(shù)為零, 在下一次垃圾回收時, 就會被釋放掉.

<pre class="custom">`let obj = {}; // obj計數(shù)為0
let a = obj; // obj計數(shù)為1
let b = obj; // obj計數(shù)為2
let a = null; // obj計數(shù)為1
let b = null; // Obj計數(shù)為0, 下次垃圾回收將obj內(nèi)存釋放

以上代碼演示的就是引用計數(shù)法垃圾回收機制, 當存在循環(huán)引用的情況就沒救了

<pre class="custom">`let obj = {};
let obj2 = {};
obj.o = obj2; // obj2計數(shù)為1
obj2.o = obj; // obj計數(shù)為1

這就是循環(huán)引用, 所以垃圾回收機制并不會對obj, obj2進行內(nèi)存釋放, 變量常駐內(nèi)存, 導致內(nèi)存泄漏.

那么說完了引用計數(shù)法, 我們再來看看主流瀏覽器目前所用的垃圾回收算法 – 標記清除法,

從2012年起,所有現(xiàn)代瀏覽器都使用了標記清除垃圾回收算法。所有對JavaScript垃圾回收算法的改進都是基于標記-清除算法的改進,并沒有改進標記-清除算法本身和它對“對象是否不再需要”的簡化定義。

標記清除法

這個算法假定設置一個叫做根(root)的對象(在Javascript里,根是全局對象)。垃圾回收器將定期從根開始,找所有從根開始引用的對象,然后繼續(xù)找這些對象引用的對象.

在開始說標記清除法之前, 補說一個知識點, 就是棧和堆的概念, 看看下面的例子

<pre class="custom">`let obj = {};
let obj2 = {};

obj = null;
obj2 = null;

我們知道, 在javascript中, 除了八大基本類型(截至目前為止是八種), 剩下的都是對象類型, 在js中對象類型都是引用類型, 內(nèi)容的實體是存在堆中的, 如下面我畫的這張圖所示:

當我們重新賦值obj, obj1的時候內(nèi)存結構會變成這樣

堆內(nèi)存中的對象沒有人引用他們, 但是他們還占用這內(nèi)存, 這時候就需要我們的垃圾回收出場銷毀他們了, V8引擎的垃圾回收機制不僅銷毀掉堆內(nèi)存中無人引用的空間, 還會對堆內(nèi)存進行碎片整理, V8的GC(垃圾回收)工作如下面動圖所示:

V8的GC大致可以分為以下幾個步驟

第一步,通過 GC Root 標記空間中活動對象和非活動對象。目前V8采用的是可訪問性算法, 從GC Root出發(fā)遍歷所有的對象, 通過GC Root可以遍歷到的標記為可訪問的, 稱為活動對象,必須保留在內(nèi)存中, GC Root無法遍歷到的標記為不可訪問的, 稱為非活動對象, 這些不可訪問的對象將會被GC清理掉.

第二步,回收非活動對象所占據(jù)的內(nèi)存。其實就是在所有的標記完成之后,統(tǒng)一清理內(nèi)存中所有被標記為可回收的對象。

第三步,做內(nèi)存整理。一般來說,頻繁回收對象后,內(nèi)存中就會存在大量不連續(xù)空間,我們把這些不連續(xù)的內(nèi)存空間稱為內(nèi)存碎片。

受代際假說的影響, V8引擎采用兩個垃圾回收器, 主垃圾回收器–Major GC、副垃圾回收器–Minor GC(Scavenger), 你可能會問什么是代際假說:

第一個是大部分對象都是“朝生夕死”的,也就是說大部分對象在內(nèi)存中存活的時間很短,比如函數(shù)內(nèi)部聲明的變量,或者塊級作用域中的變量,當函數(shù)或者代碼塊執(zhí)行結束時,作用域中定義的變量就會被銷毀。因此這一類對象一經(jīng)分配內(nèi)存,很快就變得不可訪問;

第二個是不死的對象,會活得更久,比如全局的 window、DOM、Web API 等對象。

這兩個回收器的作用如下:

  • 主垃圾回收器 -Major GC,主要負責老生代的垃圾回收。
  • 副垃圾回收器 -Minor GC (Scavenger),主要負責新生代的垃圾回收。

這里又會引出新生代內(nèi)存和老生代內(nèi)存的概念, 將堆內(nèi)存分成兩塊區(qū)域

新生代的內(nèi)存區(qū)域一般比較小, 但是垃圾回收得會比較頻繁, 而老生代內(nèi)存區(qū)的特點就是對象占用空間相對較大, 對象存活時間較長, 垃圾回收的頻率也較低.

對了補一句, 垃圾回收時是會阻塞進程的.

2、常見的內(nèi)存泄漏情況

了解垃圾回收和內(nèi)存泄漏是什么之后, 我們來看一些常見的內(nèi)存泄漏場景:

1. 意外的全局變量

前面我們提到有些對象是常駐內(nèi)存的, 視為不死對象, 如window對象, 是瀏覽器中javascript的頂級對象, 它的存在貫穿這個javascript的生命周期, 如果我們不小心把龐大又用不上的變量掛到了window對象上, 將會造成內(nèi)存泄漏, 當然這是一個很低級的錯誤.

<pre class="custom">`function test() {
  // 漏掉了聲明, 將會自動掛載到window對象下
  str = '';
  for (let i = 0; i < 100000; i++) {
   str += 'xx';
  }
  return str;
}
// test執(zhí)行結束后, str應該就沒用了, 但是它常駐在了內(nèi)存中
test();

2. 濫用閉包

此處來順便了解下閉包的概念, 閉包的概念網(wǎng)上很多說的都比較抽象, 我個人理解的閉包是:

函數(shù)和其可操作的其他作用域變量的詞法環(huán)境稱為閉包

當然了如果我是個杠精, 可能會說with語法是不是也算閉包呢? 按照定義with不是函數(shù)所以不屬于閉包.

MDN中對閉包的描述:

一個函數(shù)和對其周圍狀態(tài)(lexical environment,詞法環(huán)境)的引用捆綁在一起(或者說函數(shù)被引用包圍),這樣的組合就是閉包(closure)。也就是說,閉包讓你可以在一個內(nèi)層函數(shù)中訪問到其外層函數(shù)的作用域。在 JavaScript 中,每當創(chuàng)建一個函數(shù),閉包就會在函數(shù)創(chuàng)建的同時被創(chuàng)建出來。

閉包是靜態(tài)作用域(又稱為詞法作用域)語言獨有的功能.

<pre class="custom">`function fn() {
  const x = 'xx';
  return function() {
    return x;
 }
}
const getX = fn();
console.log(getX());

如上面的例子就是一個閉包, fn執(zhí)行結束之后內(nèi)部變量并沒有銷毀, 我們在全局作用域下可以通過getX訪問到fn函數(shù)作用域內(nèi)的變量x.

如果不好理解可以看下上面提到的靜態(tài)作用域(又稱詞法作用域), 看到靜態(tài)作用域應該你會問, 有沒有動態(tài)作用域呢, 但是是有的, bash腳本采用的就是動態(tài)作用域, javascript采用的是靜態(tài)作用域, 閉包是靜態(tài)作用域采用的功能.

看兩個例子

<pre class="custom">`const x = 123;
function fn() {
  console.log(x);
}
function fn2() {
  const x = 345;
  fn();
}
fn2(); // 結果是123

靜態(tài)作用域: fn中輸出的x所處的作用域是在定義時確定的

再看個動態(tài)作用域的例子

<pre class="custom">`# test.sh
value="global";
function fn() {
 echo $value;
}
function fn2() {
 local value="local";
 fn;
}
fn2; # 結果是local

動態(tài)作用域: fn中輸出的x所處的作用域是在調用時確定的

還有一點, 只有濫用閉包才能叫內(nèi)存泄漏, 因為根據(jù)定義只有我們用不到了, 而且沒有被銷毀的內(nèi)存才叫內(nèi)存泄漏, 閉包中的值是我們用到的所以不應該叫做內(nèi)存泄漏.

<pre class="custom">`function generateRandomMath() {
 let x = Math.random();
  return function() {
    return x;
  }
}

如上面這個例子就是濫用閉包了, 就該叫做內(nèi)存泄漏

3. 被遺忘的定時器

這個沒什么好說的, 就是設置了定時器請記住在不要的時候使用clearInterval或者clearTImeout給他關一下.

4. DOM相關

給某個dom節(jié)點綁定了很多事件, 使用過程中dom節(jié)點被移除但是被釋放內(nèi)存

我們來看個例子

<pre class="custom">`<button class="remove">remove bbb</button>
<div class="box">bbb</div>
<script> const box = document.querySelector('.box');

  document.querySelector('.remove').addEventListener('click', () => {
    document.body.removeChild(box);
    console.log(box);
  }) </script>

移除了box元素后, box仍然占用內(nèi)存, 這也是內(nèi)存泄漏, 因為box用不到了但是沒有釋放內(nèi)存

總結

到此這篇關于javascript內(nèi)存泄漏的文章就介紹到這了,更多相關javascript內(nèi)存泄漏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論