欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

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

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

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

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

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

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

那么先了解下javascript垃圾回收機(jī)制常見(jiàn)的兩種方法吧:

  • 引用計(jì)數(shù)算法
  • 標(biāo)記清除算法

引用計(jì)數(shù)法

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

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

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

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

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

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

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

那么說(shuō)完了引用計(jì)數(shù)法, 我們?cè)賮?lái)看看主流瀏覽器目前所用的垃圾回收算法 – 標(biāo)記清除法,

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

標(biāo)記清除法

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

在開始說(shuō)標(biāo)記清除法之前, 補(bǔ)說(shuō)一個(gè)知識(shí)點(diǎn), 就是棧和堆的概念, 看看下面的例子

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

obj = null;
obj2 = null;

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

當(dāng)我們重新賦值obj, obj1的時(shí)候內(nèi)存結(jié)構(gòu)會(huì)變成這樣

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

V8的GC大致可以分為以下幾個(gè)步驟

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

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

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

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

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

第二個(gè)是不死的對(duì)象,會(huì)活得更久,比如全局的 window、DOM、Web API 等對(duì)象。

這兩個(gè)回收器的作用如下:

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

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

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

對(duì)了補(bǔ)一句, 垃圾回收時(shí)是會(huì)阻塞進(jìn)程的.

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

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

1. 意外的全局變量

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

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

2. 濫用閉包

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

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

當(dāng)然了如果我是個(gè)杠精, 可能會(huì)說(shuō)with語(yǔ)法是不是也算閉包呢? 按照定義with不是函數(shù)所以不屬于閉包.

MDN中對(duì)閉包的描述:

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

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

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

如上面的例子就是一個(gè)閉包, fn執(zhí)行結(jié)束之后內(nèi)部變量并沒(méi)有銷毀, 我們?cè)谌肿饔糜蛳驴梢酝ㄟ^(guò)getX訪問(wèn)到fn函數(shù)作用域內(nèi)的變量x.

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

看兩個(gè)例子

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

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

再看個(gè)動(dòng)態(tài)作用域的例子

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

動(dòng)態(tài)作用域: fn中輸出的x所處的作用域是在調(diào)用時(shí)確定的

還有一點(diǎn), 只有濫用閉包才能叫內(nèi)存泄漏, 因?yàn)楦鶕?jù)定義只有我們用不到了, 而且沒(méi)有被銷毀的內(nèi)存才叫內(nèi)存泄漏, 閉包中的值是我們用到的所以不應(yīng)該叫做內(nèi)存泄漏.

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

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

3. 被遺忘的定時(shí)器

這個(gè)沒(méi)什么好說(shuō)的, 就是設(shè)置了定時(shí)器請(qǐng)記住在不要的時(shí)候使用clearInterval或者clearTImeout給他關(guān)一下.

4. DOM相關(guān)

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

我們來(lái)看個(gè)例子

<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)存泄漏, 因?yàn)閎ox用不到了但是沒(méi)有釋放內(nèi)存

總結(jié)

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

相關(guān)文章

最新評(píng)論