通過(guò)兩個(gè)面試題解析JS底層原理

先來(lái)看一個(gè)例子:

setTimeOut(()=>{
    console.log('set1');
});
new Promise((resolve,reject)=>{
    console.log('p1')
  resolve();
}).then(()=>{
    console.log('then1');
})
console.log('1');

問(wèn):執(zhí)行順序?
答案:p1 --> 1 --> then1 --> set1 ;
new Promise() 的時(shí)候是同步執(zhí)行。

微任務(wù):

Promise,process.nextTick

宏任務(wù):

整體的Scirpt代碼,setTimeOut , setInterval

看看如下例子:


image.png

答案:pr1 ---> 2 ---> then1 ---> set1 --->then2 ---> then 4 ---> set2

解析過(guò)程:
確保微任務(wù)執(zhí)行完了才去執(zhí)行宏任務(wù)。

一 、js執(zhí)行機(jī)制

image.png

另一個(gè)例子:
問(wèn):下面的代碼執(zhí)行會(huì)出現(xiàn)什么樣的結(jié)果?

var b = [];
for(var i=0;i<15;i++){
    b.push(new Array(20*1024*1024));
}

結(jié)果,js內(nèi)存不夠了,V8引擎的內(nèi)存溢出,崩潰了。


image.png

so,V8引擎內(nèi)存有多大?


image.png

image.png

疑問(wèn):為什么64位的系統(tǒng)就只設(shè)置1.4G?設(shè)計(jì)的這么???

原因:js回收的時(shí)候會(huì)中斷執(zhí)行。js回收100MB內(nèi)存,則需要3ms。所以不能設(shè)計(jì)太大,因?yàn)闀?huì)整個(gè)中斷掉。

疑問(wèn):為什么1.4GB 是比較合適的?

原因:js的設(shè)計(jì)是為了跑前端腳本的,不是持續(xù)的,執(zhí)行完了就沒(méi)了。而不像后端,持續(xù)執(zhí)行的,所以后端要大一些

,所以前端1.4GB是比較合適的。而且是足夠了。

概念解釋?zhuān)?/p>

新生代:新變量存放的地方

老生代:沒(méi)有被回收的老變量存放的地方

如果新生代要進(jìn)行復(fù)制更改的話,就會(huì)被放到老生代里面去。算法:如果新生代的內(nèi)存占用整個(gè)空間的25%的時(shí)候就會(huì)進(jìn)行一次復(fù)制;

那么是如何進(jìn)行復(fù)制的?

新生代有兩個(gè)空間,當(dāng)from空間大于25%的時(shí)候,會(huì)把from空間的活著的變量標(biāo)記,并且復(fù)制到新生代To空間去。然后清空f(shuō)rom空間。當(dāng)To空間大于25%的時(shí)候,在此標(biāo)記活著的變量,復(fù)制到from空間去。清空To。

啥時(shí)候新生代的變量會(huì)去老生代?

當(dāng)新生代的空間大于25%,并且還活著的變量,復(fù)制過(guò)后,就去老生代空間。

二、內(nèi)存如何回收?

image.png

當(dāng)一個(gè)變量 既不是全局,也不是局部且失去引用的時(shí)候,就會(huì)被回收。

如何查看內(nèi)存

瀏覽器:window.performance
Node:process.memoryUsage();
image.png

Node 查看內(nèi)存:

打開(kāi)終端(前提是你安裝了node):

輸入 node 進(jìn)入node環(huán)境
oricess.memoryUsage()
會(huì)顯示如下:
{
    rss:21258240,//總申請(qǐng)到的內(nèi)存
    heapTotal:5656576,//總堆內(nèi)存
    heapUsed:3051464,//已經(jīng)使用的內(nèi)存
    external:1401021//Node 和 js 不同的這個(gè)。因?yàn)閚ode的源碼是C++寫(xiě)的,可以支配一些內(nèi)存給C++,所以會(huì)有一些額外的內(nèi)存來(lái)給C++使用。
}

接下來(lái)我們做個(gè)小測(cè)試:

function getme(){
    var mem = process.memoryUsage();
    var format = function(bytes){
        return (bytes/1024/1024).toFixed(2)+"MB";
    }
    console.log("heapTotal:" + format(mem.heapTotal) + 'heapUsed:'+ format(mem.heapUsed));
}

var a =[];
var size = 20*1024*1024;
function b(){
    var arr1 = new Array(size);
    var arr2 = new Array(size);
    var arr3 = new Array(size);
    var arr4 = new Array(size);
    var arr5 = new Array(size);
}
b();
getme();
setInterval(()=>{
    a.push(new Array(20*1024*1024));
    getme();
},1000);

每一秒鐘執(zhí)行一次;我們看下執(zhí)行結(jié)果:


image.png

image.png

直到最后就崩了。。

梳理下:

最開(kāi)始的時(shí)候有800多M,因?yàn)槲覀兌x了5個(gè)局部變量并且沒(méi)被回收。當(dāng)?shù)竭_(dá)1400的時(shí)候,發(fā)現(xiàn)要回收,回收了上面5個(gè)數(shù)組,當(dāng)我們到最后,再去回收,發(fā)現(xiàn)全局只有一個(gè)a變量,無(wú)法被回收,所以最后崩潰了。

//5個(gè)全局變量 如下:
var size = 20*1024*1024;
var arr1 = new Array(size);
var arr2 = new Array(size);
var arr3 = new Array(size);
var arr4 = new Array(size);
var arr5 = new Array(size);

三、容易引發(fā)內(nèi)存使用不當(dāng)?shù)牟僮?/h2>
image.png

1、反面教材1(濫用全局變量):

var size = 20*1024*1024;
var arr1 = new Array(size);
var arr2 = new Array(size);
var arr3 = new Array(size);
var arr4 = new Array(size);
var arr5 = new Array(size);
var arr6 = new Array(size);
var arr7 = new Array(size);
var arr8 = new Array(size);
var arr9 = new Array(size);
var arr10 = new Array(size);
var arr11 = new Array(size);
var arr12 = new Array(size);
var arr13 = new Array(size);
var arr14 = new Array(size);
var arr15 = new Array(size);

然后運(yùn)行,直接報(bào)錯(cuò),內(nèi)存溢出。
如何正確的使用全局變量?

var size = 20*1024*1024;
var arr1 = new Array(size);
//使用完后釋放
arr1 = undifined; //釋放內(nèi)存

額外小知識(shí):

undifined 和 null 的區(qū)別:
null 是個(gè)關(guān)鍵字
undifined 是一個(gè)變量,和我們平時(shí)定義的 var a,bc 一樣
image.png

我們測(cè)試如下代碼:

var size = 20*1024*1024;
var arr1 = new Array(size);
arr1 = undifined;
var arr2 = new Array(size);
arr2 = undifined;
var arr3 = new Array(size);
arr3 = undifined;
var arr4 = new Array(size);
arr4 = undifined;
var arr5 = new Array(size);
arr5 = undifined;
var arr6 = new Array(size);
arr6 = undifined;
var arr7 = new Array(size);
arr7 = undifined;
var arr8 = new Array(size);
var arr9 = new Array(size);
var arr10 = new Array(size);
var arr11 = new Array(size);
var arr12 = new Array(size);
var arr13 = new Array(size);
var arr14 = new Array(size);
var arr15 = new Array(size);

運(yùn)行后正常沒(méi)有報(bào)錯(cuò):


image.png

所以,盡量不要定義全局變量,如果一定要定義,那么定義完了,請(qǐng)釋放。

2、反面教材2(緩存不限制):

緩存一定是全局的,如果不限制,早晚都會(huì)崩潰的。尤其是服務(wù)端,緩存一定要設(shè)置大小。
比較好的處理方式,給緩存加鎖。

var b = [];
for(var i=0;i<15;i++){
    b.push(new Array(20*1024*1024));
}

運(yùn)行后是崩潰的


image.png

改成如下:

var b = [];
for(var i=0;i<15;i++){
    if(b.length > 4){
        a.shift();  
    }
    b.push(new Array(20*1024*1024));
}

運(yùn)行后正常,避免無(wú)限制的緩存造成內(nèi)存崩潰。

緩存是你優(yōu)化的好幫手,如果你一定要用的話,請(qǐng)?jiān)诰彺嫔霞右粋€(gè)鎖。

3、反面教材3(操作大文件):

前端上傳大文件,如果不處理 會(huì)直接卡死,

解決辦法:切片上傳;

使用node讀取大文件的時(shí)候,如果是你使用

fs.readFile();  

將會(huì)一次性將大文件讀取到內(nèi)存中,文件較大會(huì)發(fā)生卡頓或者崩潰。

4、擴(kuò)展知識(shí)點(diǎn),老生代的回收算法:

新生代算法,標(biāo)記活的復(fù)制到另一個(gè)空間去,犧牲空間,換取時(shí)間。

老生代則不可能,因?yàn)橛?.4G,老生代則是,標(biāo)記-刪除-整理的操作。

新生代:標(biāo)記活著的變量

老生代:標(biāo)記死亡變量,當(dāng)內(nèi)存大了,刪除死亡的變量。整理,

[ ,1002,, 100]//如果不整理,數(shù)組這樣的就不會(huì)是連續(xù)的了,如果不經(jīng)常整理,數(shù)組就沒(méi)辦法繼續(xù)添加了。數(shù)組必須是連續(xù)的。通過(guò)整理變成[ 1002,100,,],連續(xù)的數(shù)組。

5、擴(kuò)展額外的例子:

var a ={n:1};
var b=a;
a.x = a = {n:2};

console.log(a);
console.log(b);

問(wèn):a,b分別是什么值?

答案:


image.png

解析:

對(duì)象是引用類(lèi)型的--> 所有的對(duì)象復(fù)制其實(shí)是給了這個(gè)對(duì)象的引用地址;

假設(shè) {n:2} 的地址是 1002,{n:1} 的地址 1000

當(dāng)var b = a 的時(shí)候,a,b的地址都是1000;

當(dāng)a.x=a={n:2}的時(shí)候,先會(huì)執(zhí)行a.x = a ,這時(shí)候x的地址是1000,即為a.x = {n:1,x:1000的地址};

a又等于{a:2},所以a的地址變成了1002;

所以打出來(lái)的 a = {a:2}, b={n:1,x:{n:2}};

關(guān)注我,學(xué)習(xí)更多前端原理
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 介紹JVM中7個(gè)區(qū)域,然后把每個(gè)區(qū)域可能造成內(nèi)存的溢出的情況說(shuō)明 程序計(jì)數(shù)器:看做當(dāng)前線程所執(zhí)行的字節(jié)碼行號(hào)指示器...
    jemmm閱讀 2,306評(píng)論 0 9
  • 所有知識(shí)點(diǎn)已整理成app app下載地址 J2EE 部分: 1.Switch能否用string做參數(shù)? 在 Jav...
    侯蛋蛋_閱讀 2,711評(píng)論 1 4
  • Java SE 基礎(chǔ): 封裝、繼承、多態(tài) 封裝: 概念:就是把對(duì)象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,252評(píng)論 0 8
  • 一、運(yùn)行時(shí)數(shù)據(jù)區(qū)域 Java虛擬機(jī)管理的內(nèi)存包括幾個(gè)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存:方法區(qū)、虛擬機(jī)棧、本地方法棧、堆、程序計(jì)數(shù)器,...
    加油小杜閱讀 1,588評(píng)論 1 15
  • 早晨在晨間日記小組里打完卡,有一個(gè)姑娘嘆氣說(shuō):“情緒簡(jiǎn)直是我人生第一大問(wèn)題,兩個(gè)月調(diào)整到現(xiàn)在又回到原點(diǎn)了。我也不知...
    八子草鋪閱讀 220評(píng)論 0 1

友情鏈接更多精彩內(nèi)容