先來(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
看看如下例子:

答案:pr1 ---> 2 ---> then1 ---> set1 --->then2 ---> then 4 ---> set2
解析過(guò)程:
確保微任務(wù)執(zhí)行完了才去執(zhí)行宏任務(wù)。
一 、js執(zhí)行機(jī)制

另一個(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)存溢出,崩潰了。

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


疑問(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)存如何回收?

當(dāng)一個(gè)變量 既不是全局,也不是局部且失去引用的時(shí)候,就會(huì)被回收。
如何查看內(nèi)存
瀏覽器:window.performance
Node:process.memoryUsage();

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é)果:


直到最后就崩了。。
梳理下:
最開(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);

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 一樣

我們測(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ò):

所以,盡量不要定義全局變量,如果一定要定義,那么定義完了,請(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)行后是崩潰的

改成如下:
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分別是什么值?
答案:

解析:
對(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}};
