把2016年寒假寫(xiě)的對(duì)《JavaScript高級(jí)程序設(shè)計(jì)》的筆記寫(xiě)在博客上贮聂,同時(shí)回看加修改靠柑,同時(shí)也更新到簡(jiǎn)書(shū)上。盡量一天一篇一章吓懈。
<h2>第四章 變量歼冰、作用域和內(nèi)存問(wèn)題</h2>
<h3>4.1基本類(lèi)型和引用類(lèi)型的值</h3>
ECMAScript變量可能包含兩個(gè)不同類(lèi)型數(shù)據(jù)的值:基本類(lèi)型值和引用類(lèi)型值。
基本類(lèi)型值指的是簡(jiǎn)單的數(shù)據(jù)段(Boolean類(lèi)型耻警、Number類(lèi)型隔嫡、String類(lèi)型甸怕、Undefined、Null) 引用類(lèi)型值指那些可能由多個(gè)值構(gòu)成的對(duì)象(Object類(lèi)型腮恩、Array類(lèi)型梢杭、Date類(lèi)型、RegExp類(lèi)型秸滴、Function類(lèi)型)
ES6中新增了Symbol,是JavaScript的第七種數(shù)據(jù)類(lèi)型武契。
<h4>4.1.1動(dòng)態(tài)的屬性</h4>
基本類(lèi)型值和引用類(lèi)型值的區(qū)別一:對(duì)于引用類(lèi)型值,我們可以為其添加或刪除屬性和方法荡含,但是基本類(lèi)型值沒(méi)有屬性和方法咒唆。
例子:
var person = new Object(); var name = "Nicholas"
person.name = "Nicholas"; name.age = 27;
alert(person.name); //"Nicholas" alert(name.age) //undefined
以上代碼一創(chuàng)建了一個(gè)對(duì)象并給他一個(gè)name屬性,又通過(guò)alert訪問(wèn)成功释液。代碼二給字符串name定義了一個(gè)age屬性全释,但當(dāng)我們?cè)L問(wèn)的時(shí)候會(huì)發(fā)現(xiàn)這個(gè)屬性不存在。這說(shuō)明只能給引用類(lèi)型值動(dòng)態(tài)地添加屬性误债,以便將來(lái)使用溪食。
<h4>4.1.2復(fù)制變量值</h4>
基本類(lèi)型值和引用類(lèi)型值的區(qū)別二:在復(fù)制變量的時(shí)候逸嘀,復(fù)制基本類(lèi)型值和引用類(lèi)型值也是有區(qū)別的。如果只是復(fù)制基本類(lèi)型值,那就是簡(jiǎn)單復(fù)制到為新變量分配的位置上沒(méi)毛病阱驾。當(dāng)復(fù)制的是引用類(lèi)型的值時(shí),同樣會(huì)將存儲(chǔ)在變量對(duì)象中的值復(fù)制一份放到為新變量分配的空間中吊趾。不同的是彩库,這個(gè)新的副本實(shí)際上是一個(gè)指針,復(fù)制結(jié)束后槽惫,兩個(gè)變量實(shí)際上將引用同一個(gè)對(duì)象周叮。因此,如果復(fù)制的是引用類(lèi)型值界斜,當(dāng)改變其中一個(gè)變量仿耽,就會(huì)影響另一個(gè)變量。例子:
var obj1 = new Object();
var obj2 = obj1;
obj1.name="Nicholas";
alert(obj2.name); //"Nicholas"
變量對(duì)象中的變量保存在堆中的對(duì)象之間的關(guān)系如圖:
圖片來(lái)自《JavaScript高級(jí)程序設(shè)計(jì)》
可以看到當(dāng)變量復(fù)制后各薇,指針仍然指向一開(kāi)始的Object项贺,而不是復(fù)制出多一個(gè)Object。.
<h4>4.1.3傳遞參數(shù)</h4>
ECMAScript中所有函數(shù)的參數(shù)都是按值傳遞的峭判。(無(wú)論參數(shù)是引用類(lèi)型值和基本類(lèi)型值)开缎。也就是說(shuō),把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù)林螃,就和4.1.2復(fù)制變量值的原理一樣奕删,把一個(gè)變量復(fù)制到另一個(gè)變量(函數(shù)的參數(shù))一樣。有不少開(kāi)發(fā)人員在這點(diǎn)會(huì)感到困惑疗认,因?yàn)樵L問(wèn)變量有按值和按引用兩種方式完残,而參數(shù)只能按值傳遞伏钠。
--------------------------討論參數(shù)傳遞的是引用類(lèi)型值的情況--------------------------
function setName(obj){
obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
以上代碼創(chuàng)建了一個(gè)對(duì)象person,這個(gè)變量被傳遞到setName()函數(shù)中后被復(fù)制給了obj谨设,在這個(gè)函數(shù)內(nèi)部熟掂,obj和person引用的是同一個(gè)對(duì)象。換句話說(shuō)铝宵,即使這個(gè)變量是按值傳遞的打掘,obj也會(huì)按引用來(lái)訪問(wèn)同一個(gè)對(duì)象(遵循4.1.2的復(fù)制變量值原理)。于是當(dāng)為函數(shù)內(nèi)部為obj添加name屬性后鹏秋,函數(shù)外部的person也會(huì)有所反映尊蚁。因?yàn)閜erson指向的對(duì)象在堆內(nèi)存中只有一個(gè),而且是全局對(duì)象侣夷。有很多開(kāi)發(fā)人員錯(cuò)誤地認(rèn)為:在局部作用域中修改的對(duì)象會(huì)在全局作用域中反映出來(lái)横朋,就說(shuō)明參數(shù)是按引用傳遞的(大錯(cuò)特錯(cuò))。為了證明對(duì)象是按值傳遞的百拓。看下面的例子:
function setName(obj){
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
這段代碼增加了兩行琴锭,為obj重新定義了一個(gè)對(duì)象,第二行為該對(duì)象定義了一個(gè)帶有不同值的name屬性衙传。如果person是按引用傳遞的决帖,那么person最后會(huì)被自動(dòng)修改為指向其name屬性值為“Greg”的新對(duì)象。但是在函數(shù)外訪問(wèn)person.name時(shí)蓖捶,顯示的值仍然是"Nicholas"地回。這說(shuō)明即使在函數(shù)內(nèi)部修改了參數(shù)的值,但原始的引用仍然保持未變俊鱼。實(shí)際上刻像,當(dāng)在函數(shù)內(nèi)部重寫(xiě)obj時(shí),這個(gè)變量引用的就是一個(gè)局部對(duì)象了(這個(gè)函數(shù)范圍的局部對(duì)象)并闲。而這個(gè)局部對(duì)象會(huì)在函數(shù)執(zhí)行完畢后立即被銷(xiāo)毀细睡。
<h4>4.1.4檢測(cè)類(lèi)型</h4>
檢測(cè)變量類(lèi)型的方法有兩種,一種是檢測(cè)基本類(lèi)型值的帝火,用typeof溜徙,另一種是檢測(cè)引用類(lèi)型值的,用instanceof犀填。
typeof操作符是確定一個(gè)變量是string,boolean,number蠢壹,undefined的最佳工具,如果變量是對(duì)象(根據(jù)規(guī)定宏浩,所有引用類(lèi)型的值都是Object的實(shí)例)或null,則typeof操作符返回的值會(huì)是“object”靠瞎。例子:
var s = "Nicholas", b = true, i = 22, u , n = null, o = new Object(), d = new Date();
alert(typeof s); //string
alert(typeof i); //number
alert(typeof b); //boolean
alert(typeof u); //undefined
alert(typeof n); //object
alert(typeof i); //object
alert(typeof d); //object
因?yàn)閠ypeof只能檢測(cè)基本類(lèi)型值比庄,檢測(cè)引用類(lèi)型值時(shí)只會(huì)返回object求妹,所以ECMAScript又提供了一個(gè)insanceof操作符,用法跟typeof不同佳窑,且只返回true 或 false制恍。
<div style="border: 1px solid #ccc; padding:10px"><strong>?另類(lèi)的情況</strong>
使用typeof操作符檢測(cè)函數(shù)時(shí),該操作符會(huì)返回"function"神凑。在Safari 5 及之前版本和Chrome 7及之前的版本中使用typeof檢測(cè)正則表達(dá)式時(shí)净神,由于規(guī)范的原因,這個(gè)操作符也返回“function”溉委。ECMA-262規(guī)定任何在內(nèi)部實(shí)現(xiàn) [ [ call ] ] 方法的對(duì)象都應(yīng)該在應(yīng)用typeof操作符時(shí)返回“function“鹃唯。由于上述瀏覽器(Safari 5,Chrome 7)中的正則表達(dá)式也實(shí)現(xiàn)了這個(gè)方法,因此對(duì)正則表達(dá)式應(yīng)用typeof會(huì)返回“function”瓣喊。在IE和Firefox中坡慌,對(duì)正則表達(dá)式應(yīng)用typeof會(huì)返回“object”.</div>
如果變量是給定引用類(lèi)型(根據(jù)它的原型鏈來(lái)識(shí)別,第6章將介紹原型鏈藻三。#原書(shū)句)的實(shí)例那么instanceof操作符就會(huì)返回true洪橘。例子:
alert(person instanceof Object); //變量person是Object嗎?
alert(colors instanceof Array); //變量colors是Array嗎棵帽?
alert(pattern instanceof RegExp); //變量pattern是RegExp嗎熄求?
//親測(cè)左右兩邊位置不可互換,互換不會(huì)出現(xiàn)提示框
因?yàn)楦鶕?jù)規(guī)定逗概,所有引用類(lèi)型的值都是Object的實(shí)例弟晚,因此把Date,Array仗谆,RegExp等引用類(lèi)型值用instanceof 與Object驗(yàn)證時(shí)指巡,始終都會(huì)返回true。用instanceof操作符檢測(cè)基本類(lèi)型值時(shí)隶垮,該操作符時(shí)鐘返回false藻雪,因?yàn)榛绢?lèi)型不是對(duì)象。
<h3>4.2執(zhí)行環(huán)境及作用域</h3>
作用域鏈重要的一點(diǎn)就是內(nèi)部執(zhí)行環(huán)境可以使用其外部環(huán)境的變量和函數(shù)狸吞,并且可以改變那個(gè)變量的值勉耀,只要那個(gè)變量不是被當(dāng)作參數(shù)傳進(jìn)去的而是直接使用的。(當(dāng)作參數(shù)傳入的是按值傳遞蹋偏,改變的是復(fù)制出來(lái)的變量便斥,不會(huì)改變?cè)瓉?lái)的變量)
執(zhí)行環(huán)境(execution context)和作用域其實(shí)超級(jí)簡(jiǎn)單。每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象(variable object)威始,環(huán)境變量中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中枢纠。但是我們無(wú)法用代碼訪問(wèn)到這個(gè)變量對(duì)象。但解析器在處理數(shù)據(jù)時(shí)會(huì)在后臺(tái)使用它黎棠。
全局執(zhí)行環(huán)境是最外圍的執(zhí)行環(huán)境晋渺。根據(jù)ECMAScript實(shí)現(xiàn)所在的宿主環(huán)境不同镰绎,表示執(zhí)行環(huán)境的對(duì)象也不一樣。在Web瀏覽器中木西,全局執(zhí)行環(huán)境被認(rèn)為是window對(duì)象(第七章將詳細(xì)討論)畴栖,因此,所有全局變量和函數(shù)都是作為window對(duì)象的屬性和方法創(chuàng)建的(window對(duì)象是個(gè)變量對(duì)象八千,全局變量和函數(shù)是它的屬性和方法)吗讶。某個(gè)執(zhí)行環(huán)境(例如一個(gè)函數(shù))中的所有代碼執(zhí)行完畢后,該環(huán)境被銷(xiāo)毀恋捆,保存在其中的所有變量和函數(shù)定義也隨之銷(xiāo)毀(全局執(zhí)行環(huán)境直到應(yīng)用程序退出(網(wǎng)頁(yè)關(guān)閉或?yàn)g覽器關(guān)閉時(shí)才被銷(xiāo)毀))
在Node.js中的全局執(zhí)行環(huán)境是global
每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境照皆。當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會(huì)被推入一個(gè)環(huán)境棧中鸠信。而在函數(shù)執(zhí)行之后纵寝,棧將其環(huán)境彈出,把控制權(quán)交給之前的執(zhí)行環(huán)境星立。ECMAScript程序中的執(zhí)行流正是由這個(gè)方便的機(jī)制在控制爽茴。
當(dāng)代碼在其中一個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈(scope chain)绰垂。作用域鏈的最前端室奏,始終都是當(dāng)前執(zhí)行代碼所在環(huán)境的變量對(duì)象。如果這個(gè)環(huán)境是函數(shù)劲装,則將其活動(dòng)對(duì)象(activation object)作為變量對(duì)象胧沫。活動(dòng)對(duì)象在最開(kāi)始時(shí)只包含一個(gè)變量占业,即arguments對(duì)象(這個(gè)對(duì)象在全局環(huán)境中不存在)绒怨。作用域鏈中的下一個(gè)變量對(duì)象來(lái)自包含它的外部環(huán)境,而再下一個(gè)變量對(duì)象則來(lái)自下一個(gè)包含環(huán)境谦疾。這樣南蹂,一直延伸到全局執(zhí)行環(huán)境;
標(biāo)識(shí)符解析是沿著作用域鏈一級(jí)一級(jí)地搜索標(biāo)識(shí)符的過(guò)程念恍。搜索過(guò)程從作用域鏈最前端開(kāi)始六剥,然后逐級(jí)向后回溯,直到找到標(biāo)識(shí)符為止(如果找不到峰伙,就會(huì)發(fā)生錯(cuò)誤)
例子:
var color = "blue"疗疟;
function changeColor(){
if(color == "blue"){
color = "red";
}
}
changeColor();
alert(color); // "red"
在這個(gè)例子中,函數(shù)changeColor( )的作用域鏈包含兩個(gè)對(duì)象:它自己的變量對(duì)象(其中定義著arguments對(duì)象)和全局環(huán)境的變量對(duì)象瞳氓〔咄可以在函數(shù)內(nèi)部訪問(wèn)變量color,就是因?yàn)榭梢栽谶@個(gè)作用域鏈中找到它。
此外店诗,在局部作用域中定義的變量可以在局部環(huán)境中與全局變量互換使用叽赊,如下面這個(gè)例子所示:
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
//這里可以訪問(wèn)color,anotherColor 和 tempColor
}
//這里可以訪問(wèn)color和anotherColor必搞,但不能訪問(wèn)tempColor
swapColors();
}
//這里只能訪問(wèn)color
changeColor();```
以上代碼涉及三個(gè)執(zhí)行環(huán)境:全局環(huán)境、changeColor()的局部環(huán)境和swapColor() 的局部環(huán)境囊咏。swapColor的局部變量中有一個(gè)變量tempColor恕洲,該變量只有在swapColor環(huán)境中能訪問(wèn)到,但是swapColor()內(nèi)部可以訪問(wèn)其他兩個(gè)環(huán)境中的所有變量梅割。
越在內(nèi)部的局部環(huán)境霜第,作用域鏈越長(zhǎng)。對(duì)于這個(gè)例子中的swapColor()而言户辞,其作用域鏈中包含3個(gè)對(duì)象:swapColor( )的變量對(duì)象泌类、changeColor()的變量對(duì)象和全局對(duì)象。swapColor()的局部環(huán)境開(kāi)始時(shí)會(huì)現(xiàn)在自己的變量對(duì)象中搜索變量和函數(shù)名底燎,如果搜索不到則再搜搜上一級(jí)作用域鏈刃榨。changeColor()的作用域鏈中只包含兩個(gè)對(duì)象:它自己的變量對(duì)象和全局對(duì)象。也就是說(shuō)双仍,它不能訪問(wèn)swapColor()的環(huán)境枢希。
<h4>4.2.1延長(zhǎng)作用域</h4>
有些語(yǔ)句可以在作用域鏈前端臨時(shí)增加一個(gè)變量對(duì)象,該變量對(duì)象會(huì)在代碼執(zhí)行后被移除朱沃。在兩種情況下會(huì)發(fā)生這種現(xiàn)象苞轿,具體來(lái)說(shuō),就是當(dāng)執(zhí)行流執(zhí)行到下列任何一個(gè)語(yǔ)句時(shí)逗物,作用域鏈會(huì)得到增長(zhǎng)
- try-catch語(yǔ)句的catch塊
- with語(yǔ)句
這兩個(gè)語(yǔ)句都會(huì)在作用域鏈的前端添加一個(gè)變量對(duì)象搬卒。對(duì)with語(yǔ)句來(lái)說(shuō),會(huì)將指定的對(duì)象添加到作用域鏈中翎卓,對(duì)catch語(yǔ)句來(lái)說(shuō)契邀,會(huì)創(chuàng)建一個(gè)新的變量對(duì)象,其中包含的是被拋出的錯(cuò)誤對(duì)象的聲明莲祸。例子:
function buildUrl(){
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
}
?添加一個(gè)with語(yǔ)句塊的知識(shí)點(diǎn)</strong><br>當(dāng)在with語(yǔ)句塊中使用方法或者變量時(shí)蹂安,程序會(huì)檢查該方法是否是本地函數(shù),變量是否是已定義變量锐帜,如果不是田盈,它將檢查偽對(duì)象(with的參數(shù)),看它是否為該對(duì)象的方法缴阎,屬性允瞧。如上面例子with語(yǔ)句塊中的href,本地?zé)o定義,則with語(yǔ)句塊會(huì)自動(dòng)加上location.href述暂,所以href實(shí)際上為href痹升。這個(gè)就是with的功能。with 語(yǔ)句是運(yùn)行緩慢的代碼塊畦韭,尤其是在已設(shè)置了屬性值時(shí)疼蛾。大多數(shù)情況下,如果可能艺配,最好避免使用它察郁。
<br>
在此,with語(yǔ)句接收的是Location對(duì)象转唉,因此其變量對(duì)象中就包含了location對(duì)象的所有屬性和方法皮钠,而這個(gè)變量對(duì)象被添加到了作用域鏈的最前端,buildUrl()函數(shù)中定義了一個(gè)變量qs赠法。當(dāng)在with語(yǔ)句中引用變量href時(shí)(實(shí)際引用的是location.href)麦轰。可以在當(dāng)前執(zhí)行環(huán)境的變量對(duì)象中找到砖织。當(dāng)引用變量qs時(shí)款侵,引用的則是在buildUrl( )中定義的那個(gè)變量,而該變量位于函數(shù)環(huán)境的變量對(duì)象中侧纯。至于with語(yǔ)句的內(nèi)部喳坠,則定義了一個(gè)名為url的變量,因而url就成了函數(shù)執(zhí)行環(huán)境的一部分茂蚓,所以可以作為函數(shù)的值被返回壕鹉。
<h4>4.2.2沒(méi)有塊級(jí)作用域</h4>
<strong>?添塊級(jí)作用域</strong><br>任何一對(duì)花括號(hào)中的語(yǔ)句都屬于一個(gè)塊聋涨,在這之中定義的所有變量在代碼塊之外都是不可見(jiàn)的晾浴,我們稱(chēng)之為塊級(jí)作用域。
<br>
**作用域有兩種牍白,塊級(jí)作用域和函數(shù)作用域**
講到這就好理解脊凰。JS沒(méi)塊級(jí)作用域就是說(shuō)在for循環(huán)和if語(yǔ)句塊中定義的變量是可見(jiàn)的,可以被外部使用的茂腥,但像其他的語(yǔ)言Java,C,C#語(yǔ)言中狸涌,在for,if語(yǔ)句中定義的變量在語(yǔ)句執(zhí)行完畢之后就會(huì)被**銷(xiāo)毀**最岗。但在JavaScript中帕胆,if語(yǔ)句中的變量聲明會(huì)將變量添加到當(dāng)前執(zhí)行環(huán)境中。注意只是當(dāng)前執(zhí)行環(huán)境般渡,如果for循環(huán)是在一個(gè)函數(shù)里懒豹,則定義的i在函數(shù)里是確定的數(shù)芙盘,在全局環(huán)境中仍然是not defined。例子:
```if(true){
var color = "blue";
}
alert(color) //"blue"
for(var i=0; i<0; i++){<br>
dosomething;<br>}
alert(i) //10<br>
function add(){
for (var i = 0; i<10; i++){<br>
dosomething;
}<br>
alert(i); //10<br>
}
add();<br>
alert(i); // **全局環(huán)境中 i is not defined**
但是ECMA2015有辦法解決沒(méi)有塊級(jí)作用域這個(gè)問(wèn)題脸秽,就是用 let 或 const代替 var 去聲明 i 儒老。這樣 i 就只會(huì)在for循環(huán)中被聲明,for循環(huán)結(jié)束之后就會(huì)被銷(xiāo)毀记餐。
1驮樊、聲明變量
使用var聲明的變量會(huì)自動(dòng)添加到最接近的環(huán)境中。在函數(shù)內(nèi)部片酝,最接近的環(huán)境就是函數(shù)局部環(huán)境巩剖。在with語(yǔ)句中,最接近環(huán)境是函數(shù)環(huán)境钠怯。如果初始化變量時(shí)沒(méi)有使用var聲明,該變量會(huì)自動(dòng)被添加到全局環(huán)境曙聂。在編寫(xiě)JavaScript的過(guò)程中晦炊,不聲明而直接初始化變量是一個(gè)常見(jiàn)的錯(cuò)誤做法,不建議這樣做宁脊,在嚴(yán)格模式下断国,初始化未經(jīng)聲明的變量會(huì)導(dǎo)致錯(cuò)誤。
2榆苞、JavaScript的查詢(xún)標(biāo)識(shí)符機(jī)制
就是當(dāng)要讀取或?qū)懭胍粋€(gè)標(biāo)識(shí)符時(shí),會(huì)通過(guò)搜索來(lái)確定標(biāo)識(shí)符薄疚,搜索過(guò)程會(huì)從作用域鏈的最前端開(kāi)始街夭,向上逐級(jí)查找板丽,如果在局部環(huán)境中找到了該標(biāo)識(shí)符趁尼,則搜索過(guò)程停止酥泞。如果一直找到全局環(huán)境仍未找到這個(gè)標(biāo)識(shí)符芝囤,則意味著該變量未聲明荧飞。
<h3>4.3垃圾收集</h3>
JavaScript具有自動(dòng)垃圾收集機(jī)制叹阔,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存耳幢。而在C和C++之類(lèi)的語(yǔ)言中睛藻,開(kāi)發(fā)人員的一項(xiàng)基本任務(wù)就是手工跟蹤內(nèi)存的使用情況店印。編寫(xiě)JavaScript的開(kāi)發(fā)人員就不用關(guān)心內(nèi)存使用的問(wèn)題按摘。所需內(nèi)存的分配以及無(wú)用內(nèi)存的回收完全實(shí)現(xiàn)了自動(dòng)管理炫贤。
垃圾收集機(jī)制的原理很簡(jiǎn)單: 把再也不用的變量找出來(lái)兰珍,刪除掠河。為此口柳,垃圾收集器會(huì)按照固定的時(shí)間間隔周期性地執(zhí)行這一操作有滑。
具體到瀏覽器中毛好,通常有兩個(gè)垃圾收集策略肌访。
<h4>4.3.1標(biāo)記清除</h4>
IE吼驶、Firefox、Opera风钻、Chrome骡技、和Safari的JavaScript實(shí)現(xiàn)使用的都是標(biāo)記清除式的垃圾收集策略布朦,只不過(guò)垃圾收集的時(shí)間間隔不同是趴。
標(biāo)記清除(mark-and-sweep)原理:當(dāng)變量進(jìn)入環(huán)境(例如,在函數(shù)中聲明一個(gè)變量)時(shí)富雅,就將這個(gè)變量表記為“進(jìn)入環(huán)境”。被這么標(biāo)記的變量邏輯上應(yīng)該隨時(shí)可能使用到滚婉,所以是不會(huì)刪除的帅刀。而當(dāng)變量離開(kāi)環(huán)境時(shí)扣溺,則將其標(biāo)記為“離開(kāi)環(huán)境”锥余。垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記,然后嘲恍,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記佃牛。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量俘侠,原因是環(huán)境中的變量已經(jīng)無(wú)法訪問(wèn)到這些變量了。最后央星,垃圾收集器完成內(nèi)存清除工作等曼,銷(xiāo)毀那些被標(biāo)記的值并回收他們所占用的內(nèi)存空間凿蒜。
<h4>4.3.2引用計(jì)數(shù)</h4>
這種垃圾收集策略不太常用废封。
引用計(jì)數(shù)的原理簡(jiǎn)單來(lái)說(shuō)就是每引用一次該變量就對(duì)引用次數(shù)加一漂洋,當(dāng)引用的變量被替換掉就對(duì)引用次數(shù)減一刽漂。當(dāng)引用次數(shù)變?yōu)?就將該變量占用的內(nèi)存回收回來(lái)。
但因?yàn)檫@種機(jī)制在循環(huán)引用時(shí)有BUG样悟,所以現(xiàn)在已經(jīng)不用窟她。而仍然提到這個(gè)垃圾回收機(jī)制是因?yàn)镮E8及以前的瀏覽器中有一部對(duì)象并不是原生JavaScript對(duì)象震糖。例如其BOM和DOM中的對(duì)象就是使用C++以COM(component Object Model 組件對(duì)象模型)對(duì)象的形式實(shí)現(xiàn)的吊说,而COM對(duì)象的垃圾收集機(jī)制采用的就是引用技術(shù)策略疏叨。因此蚤蔓,即使IE的JavaScript引擎是使用標(biāo)記清除策略實(shí)現(xiàn)的秀又,但JavaScript訪問(wèn)的COM對(duì)象依然是基于引用計(jì)數(shù)策略的。(但其實(shí)只要不涉及循環(huán)引用宣决,引用技術(shù)策略就不會(huì)有問(wèn)題)
下面的例子展示了使用COM對(duì)象導(dǎo)致的循環(huán)引用的問(wèn)題:
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;
這個(gè)例子在一個(gè)DOM元素(element)與一個(gè)原生JavaScript對(duì)象(myObject)之間創(chuàng)建了循環(huán)引用尊沸。其中洼专,變量myObject有一個(gè)名為element的屬性指向element對(duì)象孵构;而變量element也有個(gè)屬性名叫someObject回指myObject颈墅。由于存在循環(huán)引用,即使將例子中的DOM從頁(yè)面中移除官还,它也永遠(yuǎn)不會(huì)被回收望伦。
為了避免類(lèi)似這樣的循環(huán)問(wèn)題屡谐,最好是在不使用他們的時(shí)候手工斷開(kāi)原生JavaScript對(duì)象與DOM元素之間的連接。例如這樣消除前面例子創(chuàng)建的循環(huán)引用:
myObject.element = null;
element.someObject = null;
將變量設(shè)置為null意味著切斷變量與它此前引用的值之間的連接顶伞。當(dāng)垃圾收集器下次運(yùn)行時(shí)唆貌,就會(huì)刪除這些值并回收它們占用的內(nèi)存垢乙。
IE9已經(jīng)把BOM和DOM對(duì)象都轉(zhuǎn)換成了真正的JavaScript對(duì)象酪刀。這樣,就避免了兩種垃圾收集算法并存導(dǎo)致的問(wèn)題眼滤,也消除了常見(jiàn)的內(nèi)存泄漏問(wèn)題诅需。所以這是個(gè)IE8及之前的問(wèn)題了堰塌。
<h4>4.3.3性能問(wèn)題</h4>
性能問(wèn)題就是各瀏覽器的垃圾收集機(jī)制問(wèn)題蔫仙,跟開(kāi)發(fā)人員關(guān)系不大摇邦,雖然可以手工啟動(dòng)垃圾收集機(jī)制但作者不建議我們這么做屎勘。
IE中調(diào)用window.CollectGarbage( );Opera 7 及更高版本中調(diào)用windo.opera.collect( )會(huì)立即執(zhí)行垃圾收集丑慎。
<h4>4.3.4管理內(nèi)存</h4>
有垃圾收集機(jī)制的JavaScript在編寫(xiě)時(shí)一般不用操心內(nèi)存管理問(wèn)題瓤摧。但是JavaScript在進(jìn)行內(nèi)存管理和垃圾收集時(shí)面臨的問(wèn)題還是有點(diǎn)與眾不同照弥。其中一個(gè)主要的問(wèn)題这揣,就是分配給Web瀏覽器的可用內(nèi)存數(shù)量通常比分配給桌面應(yīng)用程序的少,這樣是處于安全考慮机打,防止瀏覽器占用全部系統(tǒng)內(nèi)存導(dǎo)致系統(tǒng)崩潰片迅。內(nèi)存限制問(wèn)題不僅會(huì)影響給變量分配內(nèi)存残邀,同時(shí)還會(huì)影響調(diào)用棧以及在一個(gè)線程中能夠同時(shí)執(zhí)行的語(yǔ)句數(shù)量。
因此,確保占用最少的內(nèi)存可以讓頁(yè)面獲得更好的性能(頁(yè)面占用內(nèi)存越少芥挣,性能越好)膳汪。優(yōu)化內(nèi)存的最佳方式,就是為執(zhí)行中的代碼只保存必要的數(shù)據(jù)九秀。一旦數(shù)據(jù)不再有用遗嗽,就通過(guò)將其設(shè)置為null來(lái)釋放其引用——這個(gè)做法叫做解除引用(dereferencing)。這一做法適用于大多數(shù)全局變量和全局對(duì)象的屬性鼓蜒。局部變量本來(lái)就會(huì)在離開(kāi)執(zhí)行環(huán)境后自動(dòng)解除引用痹换,如下面這個(gè)例子所示:
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");<br>
//手工解除globalPerson的引用
globalPerson = null;```
在這個(gè)例子中娇豫,變量globalPerson取得了createPerson函數(shù)返回的值,由于函數(shù)內(nèi)的局部變量在離開(kāi)執(zhí)行環(huán)境后就**自動(dòng)解除引用**,所以不用我們顯式地解除引用,但對(duì)于全局變量globalPerson历恐,在我們不需要它之后吮旅,我們可以**手工解除引用**莺葫。不是說(shuō)解除了引用后該值就會(huì)被馬上回收贸铜,解除引用的作用是讓值脫離執(zhí)行環(huán)境蛋济,在垃圾收集器下次運(yùn)行時(shí)就會(huì)將其回收镜悉。