??按照 ECMA-262 的定義,JavaScript 的變量與其他語言的變量有很大區(qū)別。
??JavaScript 變量松散類型的本質(zhì),決定了它只是在特定時(shí)間用于保存特定值的一個(gè)名字而已。
??由于不存在定義某個(gè)變量必須要保存何種數(shù)據(jù)類型值的規(guī)則,變量的值及其數(shù)據(jù)類型可以在腳本的生命周期內(nèi)改變。
??盡管從某種角度看,這可能是一個(gè)既有趣又強(qiáng)大,同時(shí)又容易出問題的特性,但 JavaScript 變量實(shí)際的復(fù)雜程度還遠(yuǎn)不止如此。
1、基本類型和引用類型的值
??ECMAscript 變量包含兩種不同數(shù)據(jù)類型的值:基本類型值和引用類型值。
??基本類型值 指的是簡單的數(shù)據(jù)段。
??引用類型值 指那些可能有多個(gè)值構(gòu)成的對象。
??將一個(gè)值賦給變量時(shí),解析器必須確定這個(gè)值是基本類型值還是引用類型值。
??5中基本數(shù)據(jù)類型:Undefined、Null、Boolean、Number、String。這5種基本數(shù)據(jù)類型是按值訪問的,因?yàn)榭梢圆僮鞅4嬖谧兞恐械膶?shí)際的值。
??引用類型的值是保存在內(nèi)存中的對象。JavaScript 不允許直接訪問內(nèi)存中的位置,也就是說不能直接操作對象的內(nèi)存空間。在操作對象時(shí),實(shí)際上是在操作對象的引用而不是實(shí)際的對象。為此,引用類型的值是按引用訪問的(這種說法不嚴(yán)謹(jǐn),當(dāng)復(fù)制保存著對象的某個(gè)變量時(shí),操作的是對象的引用。但在為對象添加屬性時(shí),操作的是實(shí)際的對象)。
1.1、動(dòng)態(tài)的屬性
??定義基本類型值和引用類型值的方式是類似的:創(chuàng)建一個(gè)變量并未該變量賦值。當(dāng)這個(gè)值保存到變量中以后,對于引用類型的值,可以為其添加屬性和方法,也可以改變和刪除其屬性和方法。
var person = new Object();
person.name = 'jack';
console.log(person.name); // name
??以上示例,創(chuàng)建了一個(gè)對象并將其保存在了變量 person 中。然后,為該對象添加了名為 name 的屬性,并將字符串值 'jack' 賦給了這個(gè)屬性。又通過 console.log() 函數(shù)訪問了這個(gè)新屬性。如果對象不被銷毀或者這個(gè)屬性不被刪除,這個(gè)屬性將一直存在。
??不能給基本類型的值添加屬性,盡管這樣做不會(huì)導(dǎo)致任何錯(cuò)誤。
示例:
var name = 'John';
name.age = 27;
console.log(name.age); // undefined
1.2、復(fù)制變量值
??從一個(gè)變量向另一個(gè)變量復(fù)制基本類型值和引用類型值是不同的。
??當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制基本類型的值時(shí),會(huì)在變量對象上創(chuàng)建一個(gè)新值,然后把該值復(fù)制到為新變量分配的位置上。
示例:
var num1 = 5;
var num2 = num1;
??num1 中保存的值是 5,當(dāng)使用 num1 的值來初始化 num2 時(shí), num2 中也保存了值 5,但 num2 中的 5 和 num1 中的 5 是完全獨(dú)立的,該值只是 num1 中 5 的一個(gè)副本。此后,這兩個(gè) 變量可以參與任何操作而不會(huì)互相影響。
??當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制引用類型的值時(shí),同樣也會(huì)將存儲(chǔ)在變量對象中的值賦值一份放到為新變量分配的空間中。不同是是,這個(gè)值的副本實(shí)際上是一個(gè)指針,而這個(gè)指針指向存儲(chǔ)在堆中的一個(gè)對象。復(fù)制操作結(jié)束后,兩個(gè)變量實(shí)際上將引用同一個(gè)對象。因此,改變其中一個(gè)變量,就會(huì)影響另一個(gè)變量。
var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'John';
console.log(obj2.name); // 'John'
??首先,變量 obj1 保存了一個(gè)對象的新實(shí)例。然后,這個(gè)值被復(fù)制到了 obj2 中;換句話說,obj1 和 obj2 都指向同一個(gè)對象。這樣,當(dāng)為 obj1 添加了 name 屬性后,可以通過 obj2 來訪問這個(gè)屬性,因?yàn)檫@兩個(gè)變量引用的都是同一個(gè)對象。
下圖展示了保存在變量對象中的變量和保存在堆中的對象之間的關(guān)系:
1.3、傳遞參數(shù)
??ECMAscript 中所有函數(shù)的參數(shù)都是按值傳遞的。也就是說,把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù),就和把值從一個(gè)變量復(fù)制到另一個(gè)變量一樣。基本類型值的傳遞如同基本類型變量的復(fù)制一樣,而引用類型值的傳遞,則如同引用類型變量的復(fù)制一樣。
參數(shù)是按值傳遞的:
function setName (obj) {
obj.name = 'John';
obj = new Object();
obj.name = 'Jack';
}
var person = new Object();
setName(person);
console.log(person.name); // 'John'
??上述例子說明即使在函數(shù)內(nèi)部修改了參數(shù)的值,但原始的引用仍保持不變。實(shí)際上,當(dāng)在函數(shù)內(nèi)部重寫 obj 時(shí),這個(gè)變量引用的就是一個(gè)局部對象。而這個(gè)局部對象會(huì)在函數(shù)執(zhí)行完畢后立即被銷毀。
可以把 ECMAscript 函數(shù)的參數(shù)想象成局部變量
1.4、檢測類型
??要檢測一個(gè)變量是不是基本數(shù)據(jù)類型, typeof 操作符是最佳工具。更具體一點(diǎn):typeof 操作符是確定一個(gè)變量是字符串、數(shù)值、布爾值、undefine 的最佳工具。
如果變量是 對象 或 null, typeof 操作符返回 'object'。
??使用 instanceof 操作符檢測引用類型值是什么類型的對象。
語法:
result = variable instanceof constructor
??如果變量是給定引用類型(根據(jù)它的原型鏈來識(shí)別)的實(shí)例,那么 instanceof 操作符返回 true
示例:
console.log(person instanceof Object); // 變量 person 是 Object 嗎?
console.log(colors instanceof Array); // 變量 colors 是 Array嗎?
console.log(pattern instanceof RegExp); // 變量 pattern 是 RegExp嗎?
??規(guī)定:所有引用類型的值都是 Object 的實(shí)例。
??因此,在檢測一個(gè)引用類型值和 Object 構(gòu)造函數(shù)時(shí),instanceof 操作符始終都會(huì)返回 true。
??使用 instanceof 操作符檢測基本類型值,始終返回 false。
typeof 操作符檢測函數(shù)時(shí),會(huì)返回 'function'
2、執(zhí)行環(huán)境及作用域
??執(zhí)行環(huán)境(execution context,有時(shí)也簡稱 “ 環(huán)境 ”)是 JavaScript 中最為重要的一個(gè)概念。執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了它們各自的行為。
??每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對象中,雖然我們編寫的代碼無法訪問到這個(gè)對象,但解析器在處理數(shù)據(jù)時(shí)會(huì)在后臺(tái)使用它。
??全局執(zhí)行環(huán)境是最外圍的一個(gè)執(zhí)行環(huán)境。根據(jù) ECMAscript 實(shí)現(xiàn)所在的宿主環(huán)境不同,表示執(zhí)行環(huán)境的對象也不一樣。在 Web 瀏覽器中,全局執(zhí)行環(huán)境被認(rèn)為是 window 對象,因此所有全局變量和函數(shù)都是作為 window 對象的屬性和方法創(chuàng)建的。某個(gè)執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境被銷毀,保存在其中的所有變量和函數(shù)定義也隨之銷毀(全局執(zhí)行環(huán)境知道應(yīng)用程序退出——例如關(guān)閉網(wǎng)頁或?yàn)g覽器——時(shí)才會(huì)被銷毀)。
??每個(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)境。
??當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對象的一個(gè)作用域鏈(scope chain)。作用域鏈的用途,是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。作用域的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對象。如果這個(gè)環(huán)境是函數(shù),則將其活動(dòng)對象(activation Object)作為變量對象。活動(dòng)對象在最開始是只包含一個(gè)變量,即 arguments 對象(這個(gè)對象在全局環(huán)境中是不存在的)。作用域鏈中的下一個(gè)變量對象來自包含(外部)環(huán)境,再下一個(gè)變量對象則來自下一個(gè)包含環(huán)境。這樣,一直延續(xù)到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈中的最后一個(gè)對象。
??標(biāo)識(shí)符解析是沿著作用域鏈一級一級地搜索標(biāo)識(shí)符的過程。搜索過程始終從作用域鏈的前端開始,逐級地向后回溯,直至找到標(biāo)識(shí)符為止(如果找不到標(biāo)識(shí)符,通常會(huì)導(dǎo)致錯(cuò)誤發(fā)生)。
示例:
var color = blue;
function changeColor () {
if (color === 'blue') {
color = 'red';
} else {
color = 'blue';
}
}
changeColor();
console.log('Color is now ' + color);
??在上述例子中,函數(shù) changeColor() 的作用域包含兩個(gè)對象:它自己的變量對象(其中定義著 arguments 對象)和全局環(huán)境的變量對象。可以在函數(shù)內(nèi)部訪問變量 color ,就是因?yàn)榭梢栽谶@個(gè)作用域鏈中找到它。
??此外,在局部作用域中定義的變量可以在局部環(huán)境中與全局變量互換使用,示例:
var color = 'blue';
function changeColor () {
var anotherColor = 'red';
function swapColors () {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 這里可以訪問 color、anotherColor、tempColor
}
// 這里可以訪問 color 和 anotherColor,但不能訪問 tempColor
swapColors();
}
// 這里只能訪問color
changeColor();
??上述代碼共涉及 3 個(gè)執(zhí)行環(huán)境:全局環(huán)境、changeColor() 的局部環(huán)境和 swapColors() 的局部環(huán)境。
??全局環(huán)境中有一個(gè)變量 color 和一個(gè)函數(shù) changeColor() 。
??changeColor() 的局部環(huán)境中有一個(gè)名為 anotherColor 的變量和一個(gè)名為 swapColors() 的函數(shù),但它也可以訪問全局環(huán)境中的變量 color。
??swapColors() 的局部環(huán)境中有一個(gè)變量 tempColor,該變量只能在這個(gè)環(huán)境中訪問到,無論全局環(huán)境還是 changeColor() 的局部環(huán)境都無權(quán)訪問 tempColor。在 swapColors() 內(nèi)部則可以訪問其他兩個(gè)環(huán)境中的所有變量,因?yàn)槟莾蓚€(gè)環(huán)境是它的父執(zhí)行環(huán)境。
??上面例子的作用域鏈:
??上圖中的矩形表示特定的執(zhí)行環(huán)境。內(nèi)部環(huán)境可以通過作用域鏈訪問所有的外部環(huán)境,但外部環(huán)境不能訪問到內(nèi)部環(huán)境中的任何變量和函數(shù)。
這些環(huán)境之間的聯(lián)系是線性、有次序的。每個(gè)環(huán)境都可以向上搜索作用域鏈,以查詢變量和函數(shù)名;任何環(huán)境都不能通過向下搜索作用域鏈而進(jìn)入另一個(gè)執(zhí)行環(huán)境。
函數(shù)參數(shù)也被當(dāng)作變量來對待,因此其訪問規(guī)則與執(zhí)行環(huán)境中的其他變量相同。
2.1、延長作用域鏈
??雖然執(zhí)行環(huán)境的類型只有兩種——全局和局部(函數(shù)),但可以延長作用域鏈。
??有些語句可以作用域鏈的前端臨時(shí)增加一個(gè)變量對象,該變量會(huì)在代碼執(zhí)行后被移除。
??當(dāng)執(zhí)行流進(jìn)入下列任何一個(gè)語句時(shí),作用域鏈就會(huì)得到加長:
- try-catch 語句的 catch 塊
- with 語句
??這兩個(gè)語句都會(huì)在作用域鏈的前端添加一個(gè)變量對象。
??對 with 語句來說,會(huì)將指定的對象添加到作用域鏈中。
??對 catch 語句來說,會(huì)創(chuàng)建一個(gè)新的變量對象,其中包含的是被拋出的錯(cuò)誤對象的聲明。
2.2、查詢標(biāo)識(shí)符
??當(dāng)在某個(gè)環(huán)境中為了讀取或?qū)懭攵靡粋€(gè)標(biāo)識(shí)符時(shí),必須通過搜索來確定該標(biāo)識(shí)符實(shí)際代表什么。搜索過程沖作用域鏈的前端開始,向上逐級查詢與給定名字匹配的標(biāo)識(shí)符。如果在局部環(huán)境中找到了該標(biāo)識(shí)符,搜索過程停止,變量就緒。如果在局部環(huán)境中沒有找到該變量名,則繼續(xù)沿作用域鏈向上搜索。搜索過程將一直追溯到全局環(huán)境的變量對象。如果在全局環(huán)境中也沒有找到這個(gè)標(biāo)識(shí)符,則說明該變量尚未聲明。
??搜索過程中,如果存在一個(gè)局部的變量的定義,搜索就會(huì)停止,不再進(jìn)入另一個(gè)變量對象。即,如果局部環(huán)境中存在同名標(biāo)識(shí)符,就不會(huì)使用位于父環(huán)境中的標(biāo)識(shí)符。
3、垃圾收集
??JavaScript 具有自動(dòng)垃圾收集機(jī)制,也就是說,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過程中使用的內(nèi)存。所需內(nèi)存的分配以及無用內(nèi)存的回收完全實(shí)現(xiàn)了自動(dòng)管理。
??垃圾收集機(jī)制原理:找出那些不再繼續(xù)使用的變量,然后釋放其占用的內(nèi)存。
??為此,垃圾收集器會(huì)按照固定的時(shí)間間隔(或代碼執(zhí)行中預(yù)定的收集時(shí)間),周期性的執(zhí)行這一操作。
??函數(shù)中局部變量的正常生命周期:局部變量只在函數(shù)執(zhí)行的過程中存在。在這個(gè)過程中,會(huì)為局部變量在棧(或堆)內(nèi)存上分配相應(yīng)的空間,以便存儲(chǔ)它們的值。然后在函數(shù)中使用這些變量,直至函數(shù)執(zhí)行結(jié)束。此時(shí),局部變量就沒有存在的必要了,因此可以釋放它們的內(nèi)存以共將來使用。
??為了判斷變量是否有存在的必要,垃圾收集器必須跟蹤哪個(gè)變量有用,哪個(gè)沒用,對于不再有用的變量打上標(biāo)記,以備將來收回其占用的內(nèi)存。
??用于標(biāo)識(shí)無用變量的策略可能會(huì)因?qū)崿F(xiàn)而異,但具體到瀏覽器中的實(shí)現(xiàn),則通常有兩個(gè)策略。
- 標(biāo)記清除
- 引用計(jì)數(shù)
3.1、標(biāo)記清除
??JavaScript 中最常用的垃圾收集方式是標(biāo)記清除(mark-and-sweep)。
??當(dāng)變量進(jìn)入環(huán)境(例如,在函數(shù)中聲明一個(gè)變量)時(shí),就將這個(gè)變量標(biāo)記為 “ 進(jìn)入環(huán)境 ”。從邏輯上講,永遠(yuǎn)不能釋放進(jìn)入環(huán)境的變量所占用的,因?yàn)橹灰獔?zhí)行流進(jìn)入相應(yīng)的環(huán)境,就可能會(huì)用到它們。而當(dāng)變量離開環(huán)境時(shí),則將其標(biāo)記為 “ 離開環(huán)境 ”。
可以使用任何方式來標(biāo)記變量:
- 可以通過反轉(zhuǎn)某個(gè)特殊的位來記錄一個(gè)變量何時(shí)進(jìn)入環(huán)境
- 使用一個(gè) “ 進(jìn)入環(huán)境的 ” 變量列表及一個(gè) “ 離開環(huán)境的 ” 變量列表來跟蹤哪個(gè)變量發(fā)生了變化。
??說到底,如何標(biāo)記變量并不重要,關(guā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)無法訪問到這些變量了。最后,垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。
3.2、引用計(jì)數(shù)
??另一種不太常見的垃圾收集策略叫引用計(jì)數(shù)(reference counting)。
??引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值被引用的次數(shù)。當(dāng)聲明了一個(gè)變量并將一個(gè)引用類型值賦給該變量時(shí),則這個(gè)值的引用次數(shù)就是 1。如果同一個(gè)值又被賦給另一個(gè)變量,則該值的引用次數(shù)加 1。相反,如果包含對這個(gè)值引用的變量有取得了另外一個(gè)值,則這個(gè)值的引用次數(shù)減 1,當(dāng)這個(gè)值的引用次數(shù)變成 0 時(shí),則說明沒有辦法在訪問到這個(gè)值了,因而就可以將其占用的內(nèi)存空間回收回來。這樣,當(dāng)垃圾收集器下次再運(yùn)行時(shí),它就會(huì)釋放那些引用次數(shù)為零的值多占用的內(nèi)存。
遇到問題:循環(huán)引用。
??循環(huán)引用是指對象 A 中包含一個(gè)指向?qū)ο?B 的指針,而對象 B 中也包含一個(gè)指向?qū)ο?A 的引用。示例:
function problem() {
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
??在上述例子中,objectA 和 objectB 通過各自的屬性相互引用;也就是說,這兩個(gè)對象的的引用次數(shù)都是 2。在采用標(biāo)記清除策略的實(shí)現(xiàn)中,由于函數(shù)執(zhí)行之后,這兩個(gè)對象都離開了作用域,因此這種相互引用不是個(gè)問題。但在采用引用計(jì)數(shù)策略的實(shí)現(xiàn)中,當(dāng)函數(shù)執(zhí)行完畢后,objectA 和 objectB 還將繼續(xù)存在,因?yàn)樗鼈兊囊么螖?shù)永遠(yuǎn)不會(huì)是 0。假如這個(gè)函數(shù)被重復(fù)多次調(diào)用,就會(huì)導(dǎo)致大量內(nèi)存得不到回收。
3.3、性能問題
??垃圾收集器是周期性運(yùn)行的,而且如果為變量分配的內(nèi)存數(shù)量很可觀,那么回收工作量也是相當(dāng)大的。在這種情況下,確定垃圾收集的時(shí)間間隔是一個(gè)非常重要的問題。
在有的瀏覽器中可以觸發(fā)垃圾收集過程,但不建議這樣做。
在 IE 中,調(diào)用 window.CollectGarbage() 方法會(huì)立即執(zhí)行垃圾收集。
在 Opera 7 及更高版本中,調(diào)用 window.opera.collect() 也會(huì)啟動(dòng)垃圾收集例程。
3.4、管理內(nèi)存
??使用具備垃圾收集機(jī)制的語言編寫程序,開發(fā)人員一般不必操心內(nèi)存管理問題。但是,JavaScript 在進(jìn)行內(nèi)存管理及垃圾收集時(shí)面臨的問題還是有點(diǎn)與眾不同。其中最主要的一個(gè)問題,就是分配給 Web 瀏覽器的可用內(nèi)存數(shù)量通常要比分配給桌面應(yīng)用程序的少。這樣做的目的主要是出于安全方面的考慮,目的是防止運(yùn)行 JavaScript 的網(wǎng)頁耗盡全部系統(tǒng)內(nèi)存而導(dǎo)致系統(tǒng)崩潰。內(nèi)存限制問題不僅會(huì)影響給變量分配內(nèi)存,同時(shí)還會(huì)影響調(diào)用棧以及在一個(gè)線程中能夠同時(shí)執(zhí)行的語句數(shù)量。
??因此,確保占用最少的內(nèi)存可以讓頁面獲得更好的性能。而優(yōu)化內(nèi)存占用的最佳方式,就是為執(zhí)行中的代碼只保存必要的數(shù)據(jù)。一旦數(shù)據(jù)不再有用,最好通過將其值設(shè)置為 null 來釋放其引用——這個(gè)做法叫做解除引用(dereferencing)。這一做法適用于大多數(shù)全局變量和全局對象的屬性。局部變量會(huì)在它們離開執(zhí)行環(huán)境是自動(dòng)被解除引用。
示例:
function createPerson(name) {
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson('Nicholas');
// 手工解除 globalPerson 的引用
globalPerson = null;
??在上述例子中,變量 globalPerson 取得了 createPerson() 函數(shù)的返回值。在 createPerson() 函數(shù)內(nèi)部,我們創(chuàng)建了一個(gè)對象并將其賦給局部變量 localPerson,然后又為該對象添加了一個(gè)名為 name 的屬性。最后,當(dāng)調(diào)用這個(gè)函數(shù)時(shí),localPerson 以函數(shù)值的形式返回并賦給全局變量 globalPerson。由于 localPerson 在 createPerson() 函數(shù)執(zhí)行完畢后就離開了其執(zhí)行環(huán)境,因此無需我們顯示地去為它解除引用。但是對于全局變量 globalPerson 而言,則需要我們在不使用它的時(shí)候手工為它解除引用,這也是上面例子中最后一行代碼的目的。
??不過,解除一個(gè)值的引用并不意味著自動(dòng)回收該值所占用的內(nèi)存。解除引用的真正作用是讓值脫離執(zhí)行環(huán)境,以便垃圾收集器下次運(yùn)行時(shí)將其回收。
小結(jié)
??JavaScript 變量可以用來保存兩種類型的值:基本類型值和引用類型值。
??基本類型值源自以下 5 中基本數(shù)據(jù)類型:Undefined、Null、Boolean、Number、String。
基本類型值和引用類型值具有以下特點(diǎn):
- 基本類型值在內(nèi)存中占據(jù)固定大小的空間,因此被保存在棧內(nèi)存中。
- 從一個(gè)變量向另一個(gè)變量復(fù)制基本類型的值,會(huì)創(chuàng)建這個(gè)值的一個(gè)副本。
- 引用類型的值是對象,保存在堆內(nèi)存中。
- 包含引用類型值的變量實(shí)際上包含的并不是對象,而是一個(gè)指向該對象的指針。
- 從一個(gè)變量向另一變量復(fù)制引用類型的值,復(fù)制的其實(shí)是指針,因此兩個(gè)變量最終都指向同一個(gè)對象。
- 確定一個(gè)值是那種基本類型可以使用 typeof 操作符,確定一個(gè)值是哪種引用類型可以使用 instanceof 操作符。
??所有變量(包括基本類型和引用類型)都存在于一個(gè)執(zhí)行環(huán)境(也稱為作用域)當(dāng)中,這個(gè)執(zhí)行環(huán)境決定了變量的生命周期,以及哪一部分代碼可以訪問其中的變量。
執(zhí)行環(huán)境:
- 執(zhí)行環(huán)境有全局執(zhí)行環(huán)境(也稱為全局環(huán)境)和函數(shù)執(zhí)行環(huán)境之分。
- 每次進(jìn)入一個(gè)新執(zhí)行環(huán)境,都會(huì)創(chuàng)建一個(gè)用于搜索變量和函數(shù)的作用域鏈。
- 函數(shù)的局部環(huán)境不僅有權(quán)訪問函數(shù)作用域中的變量,而且有權(quán)訪問其包含(父)環(huán)境,乃至全局環(huán)境。
- 全局環(huán)境只能訪問在全局環(huán)境中定義的變量和函數(shù),而不能之間訪問局部環(huán)境中的任何數(shù)據(jù)。
- 變量的執(zhí)行環(huán)境有助于確定應(yīng)該何時(shí)釋放內(nèi)存。
??JavaScript 是一門具有自動(dòng)垃圾收集機(jī)制的編程語言,開發(fā)人員不必關(guān)心內(nèi)存分配和回收問題。可以對 JavaScript 的垃圾收集例程做如下總結(jié):
- 離開作用域的值將被自動(dòng)標(biāo)記為可以回收,因此將在垃圾收集期間被刪除。
- “ 標(biāo)記清除 ” 是目前主流的垃圾收集算法,這種算法的思想是給當(dāng)前不使用的值加上標(biāo)記,然后再回收其內(nèi)存。
- 另一種垃圾收集算法是 “ 引用計(jì)數(shù) ”,這種算法的思想是跟蹤記錄所有值被引用的次數(shù)。JavaScript 引擎目前都不再使用這種算法;但在 IE 訪問非原生 JavaScript 對象(IE9 以下,如 DOM 元素)時(shí),這種算法仍然可能會(huì)導(dǎo)致問題。
- 當(dāng)代碼中存在循環(huán)引用現(xiàn)象時(shí),“ 引用計(jì)數(shù) ” 算法就會(huì)導(dǎo)致問題。
- 解除變量的引用不僅有助于消除循環(huán)引用現(xiàn)象,而且對垃圾收集也有好處。為了確保有效地回收內(nèi)存,應(yīng)該及時(shí)解除不再使用的全局對象、全局對象屬性以及循環(huán)引用變量的引用。