在JavaScript中,會(huì)有聽到兩個(gè)概念:堆棧溢出和內(nèi)存泄漏,這兩種機(jī)制在開發(fā)中遇到的不多,但是一旦碰到就很頭疼。下面就分別來講述一下二者的概念,觸發(fā)原因以及解決辦法。
堆棧溢出:
什么是堆棧溢出?我們知道JS中的數(shù)據(jù)存儲(chǔ)分為棧和堆,程序代碼運(yùn)行都需要一定的計(jì)算存儲(chǔ)空間,就是棧了,棧遵循先進(jìn)后出的原則,所以程序從棧底開始運(yùn)行計(jì)算,程序內(nèi)部函數(shù)的調(diào)用以及返回會(huì)不停的執(zhí)行進(jìn)棧和出棧的操作,棧內(nèi)被所占的資源也在不斷的對(duì)應(yīng)變化,但是一旦你的調(diào)用即進(jìn)棧操作過多,返回即出棧不夠,這時(shí)候就會(huì)導(dǎo)致棧滿了,再進(jìn)棧的就會(huì)溢出來。打個(gè)比方:就好像是跟女朋友去吃火鍋,點(diǎn)了一個(gè)鴛鴦鍋,倆人開始一人吃辣的一人吃不辣的,很和諧,結(jié)果你貪吃,放了很多菜在辣的一邊,你又吃不過來,辣油溢出來到不辣的一邊,那么你的女朋友就不高興了,最終你肯定會(huì)被罵了。當(dāng)然了,JS堆棧溢出后不會(huì)罵你,但是他會(huì)報(bào)錯(cuò)然后罷工了。再來看一個(gè)網(wǎng)上使用比較多模擬的代碼(遞歸)的例子:
function isEven(n) {
if (n === 0) {
return true;
}
if (n === 1) {
return false;
}
return isEven(Math.abs(n) - 2);
}
當(dāng)我們打印console.log(factorial(10))答案是true,結(jié)果運(yùn)行也比較快,再看當(dāng)我們輸入console.log(factorial(10000000)),結(jié)果是拋出了錯(cuò)誤:Uncaught RangeError: Maximum call stack size exceeded(此運(yùn)行在谷歌瀏覽器測(cè)試),這個(gè)錯(cuò)誤的意思就是:最大調(diào)用超過堆棧大小,這是為什么呢?原因就是,程序在執(zhí)行代碼過程中,需要一定的計(jì)算空間即棧,一般大小為1M左右,當(dāng)你每次調(diào)用程序內(nèi)的函數(shù)等其它時(shí),這些就會(huì)占用一定的空檢,當(dāng)占用過多時(shí),就會(huì)超過該程序所分配的棧的空間,就會(huì)報(bào)錯(cuò)了。那么,如何解決這個(gè)問題?就拿上面的遞歸例子來說,解決辦法如下(前文我們提到了閉包,這里就用閉包和Trampoline(蹦床原理)來解決):
function isEven(n) {
function isEvenInner(n) {
if (n === 0) {
return true;
}
if (n === 1) {
return false;
}
return function () {
return isEvenInner(Math.abs(n) - 2);
}
}
function trampoline(func, arg) {
var value = func(arg);
while (typeof value === "function") {
value = value();
}
return value;
}
return trampoline.bind(null, isEvenInner)(n);
}
內(nèi)存泄漏
什么是內(nèi)存泄漏??jī)?nèi)存泄漏是指程序被分配的棧內(nèi)有一塊內(nèi)存既不能使用,也不能被回收。就是你和你女朋友吃火鍋,中間有一塊位置沒有湯,不能燙菜一樣的。導(dǎo)致內(nèi)存泄漏的原因一般有一下幾種情況:
函數(shù)內(nèi)未使用聲明變量關(guān)鍵字的變量
function func() {
a = 1;
}
未銷毀的定時(shí)器
setInterval(function () {
console.log(1)
}, 1000);
DOM以外的節(jié)點(diǎn)引用
var elements = {
button: document.getElementById('button'),
};
function doStuff() {
button.click();
}
function removeButton() {
document.body.removeChild(document.getElementById('button')); // 這時(shí),我們?nèi)匀挥幸粋€(gè)引用指向全局中的elements。button這個(gè)節(jié)點(diǎn)仍在內(nèi)存中,不會(huì)被回收。
}
閉包的循環(huán)引用
function my(name) {
function sayName() {
console.log(name)
}
return sayName
}
var sayHi= my("tom")
sayHi() //tom
在函數(shù)my()內(nèi)部創(chuàng)建的sayName()函數(shù)是不會(huì)被回收機(jī)制回收,如果閉包不被調(diào)用,由于閉包會(huì)攜帶包含它的函數(shù)的作用域,因此會(huì)比其他函數(shù)占用更多的內(nèi)存。過度使用閉包可能會(huì)導(dǎo)致內(nèi)存占用過多。
最后再多加一個(gè)概念,我們前文一直提到程序運(yùn)行時(shí)所占的內(nèi)存空間,并且在程序運(yùn)行的時(shí)候不停的在進(jìn)行進(jìn)棧出棧,調(diào)用和銷毀,這里就涉及到瀏覽器的垃圾回收機(jī)制。什么是垃圾?垃圾就是不再使用的變量,顧名思義,你這個(gè)變量沒用了,抱歉,你就會(huì)被瀏覽器銷毀,以此來騰出空間,瀏覽器常用的垃圾回收辦法有兩種:標(biāo)記清除和引用計(jì)數(shù)。
標(biāo)記清除
這個(gè)是最常用的方式。當(dāng)變量進(jìn)入執(zhí)行時(shí),會(huì)給上一個(gè)標(biāo)記,就證明這個(gè)變量進(jìn)入了代碼的執(zhí)行環(huán)境,當(dāng)變量執(zhí)行完畢,離開執(zhí)行環(huán)境時(shí),會(huì)被標(biāo)記上離開了執(zhí)行環(huán)境。此時(shí)的垃圾回收器會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的標(biāo)記。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無(wú)法訪問到這些變量了。最后。垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值,并回收他們所占用的內(nèi)存空間。
引用計(jì)數(shù)
這種方式用的不多。引用計(jì)數(shù),顧名思義就是計(jì)算這個(gè)變量被引用的次數(shù)。當(dāng)一個(gè)變量被聲明且賦上引用類型,這個(gè)變量的引用次數(shù)就會(huì)相應(yīng)的加1,如果包含這個(gè)值的引用變量又附上了另外一個(gè)值,那么這個(gè)值的引用次數(shù)就相應(yīng)的減1,當(dāng)引用次數(shù)變?yōu)?的時(shí)候,就無(wú)法訪問這個(gè)值了。當(dāng)垃圾收集器運(yùn)行的時(shí)候,就會(huì)回收引用次數(shù)為0的值所占的內(nèi)存,并釋放這些內(nèi)存。
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對(duì)作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請(qǐng)及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對(duì)此聲明的最終解釋權(quán)。