三、JavaScript 變量、作用域和內(nèi)存問(wèn)題

??按照 ECMA-262 的定義,JavaScript 的變量與其他語(yǔ)言的變量有很大區(qū)別。

??JavaScript 變量松散類(lèi)型的本質(zhì),決定了它只是在特定時(shí)間用于保存特定值的一個(gè)名字而已。

??由于不存在定義某個(gè)變量必須要保存何種數(shù)據(jù)類(lèi)型值的規(guī)則,變量的值及其數(shù)據(jù)類(lèi)型可以在腳本的生命周期內(nèi)改變。

??盡管從某種角度看,這可能是一個(gè)既有趣又強(qiáng)大,同時(shí)又容易出問(wèn)題的特性,但 JavaScript 變量實(shí)際的復(fù)雜程度還遠(yuǎn)不止如此。

1、基本類(lèi)型和引用類(lèi)型的值

??ECMAscript 變量包含兩種不同數(shù)據(jù)類(lèi)型的值:基本類(lèi)型值和引用類(lèi)型值。
??基本類(lèi)型值 指的是簡(jiǎn)單的數(shù)據(jù)段。
??引用類(lèi)型值 指那些可能有多個(gè)值構(gòu)成的對(duì)象。
??將一個(gè)值賦給變量時(shí),解析器必須確定這個(gè)值是基本類(lèi)型值還是引用類(lèi)型值。

??5中基本數(shù)據(jù)類(lèi)型:Undefined、Null、Boolean、Number、String。這5種基本數(shù)據(jù)類(lèi)型是按值訪問(wèn)的,因?yàn)榭梢圆僮鞅4嬖谧兞恐械膶?shí)際的值。

??引用類(lèi)型的值是保存在內(nèi)存中的對(duì)象。JavaScript 不允許直接訪問(wèn)內(nèi)存中的位置,也就是說(shuō)不能直接操作對(duì)象的內(nèi)存空間。在操作對(duì)象時(shí),實(shí)際上是在操作對(duì)象的引用而不是實(shí)際的對(duì)象。為此,引用類(lèi)型的值是按引用訪問(wèn)的(這種說(shuō)法不嚴(yán)謹(jǐn),當(dāng)復(fù)制保存著對(duì)象的某個(gè)變量時(shí),操作的是對(duì)象的引用。但在為對(duì)象添加屬性時(shí),操作的是實(shí)際的對(duì)象)。

1.1、動(dòng)態(tài)的屬性

??定義基本類(lèi)型值和引用類(lèi)型值的方式是類(lèi)似的:創(chuàng)建一個(gè)變量并未該變量賦值。當(dāng)這個(gè)值保存到變量中以后,對(duì)于引用類(lèi)型的值,可以為其添加屬性和方法,也可以改變和刪除其屬性和方法。

var person = new Object();
person.name = 'jack';
console.log(person.name);   // name

??以上示例,創(chuàng)建了一個(gè)對(duì)象并將其保存在了變量 person 中。然后,為該對(duì)象添加了名為 name 的屬性,并將字符串值 'jack' 賦給了這個(gè)屬性。又通過(guò) console.log() 函數(shù)訪問(wèn)了這個(gè)新屬性。如果對(duì)象不被銷(xiāo)毀或者這個(gè)屬性不被刪除,這個(gè)屬性將一直存在。

??不能給基本類(lèi)型的值添加屬性,盡管這樣做不會(huì)導(dǎo)致任何錯(cuò)誤。
示例:

var name = 'John';
name.age = 27;
console.log(name.age);   // undefined

1.2、復(fù)制變量值

??從一個(gè)變量向另一個(gè)變量復(fù)制基本類(lèi)型值和引用類(lèi)型值是不同的。
??當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制基本類(lèi)型的值時(shí),會(huì)在變量對(duì)象上創(chuàng)建一個(gè)新值,然后把該值復(fù)制到為新變量分配的位置上。
示例:

var num1 = 5;
var num2 = num1;

??num1 中保存的值是 5,當(dāng)使用 num1 的值來(lái)初始化 num2 時(shí), num2 中也保存了值 5,但 num2 中的 5 和 num1 中的 5 是完全獨(dú)立的,該值只是 num1 中 5 的一個(gè)副本。此后,這兩個(gè) 變量可以參與任何操作而不會(huì)互相影響。

??當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制引用類(lèi)型的值時(shí),同樣也會(huì)將存儲(chǔ)在變量對(duì)象中的值賦值一份放到為新變量分配的空間中。不同是是,這個(gè)值的副本實(shí)際上是一個(gè)指針,而這個(gè)指針指向存儲(chǔ)在堆中的一個(gè)對(duì)象。復(fù)制操作結(jié)束后,兩個(gè)變量實(shí)際上將引用同一個(gè)對(duì)象。因此,改變其中一個(gè)變量,就會(huì)影響另一個(gè)變量。

var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'John';
console.log(obj2.name);    // 'John'

??首先,變量 obj1 保存了一個(gè)對(duì)象的新實(shí)例。然后,這個(gè)值被復(fù)制到了 obj2 中;換句話說(shuō),obj1 和 obj2 都指向同一個(gè)對(duì)象。這樣,當(dāng)為 obj1 添加了 name 屬性后,可以通過(guò) obj2 來(lái)訪問(wèn)這個(gè)屬性,因?yàn)檫@兩個(gè)變量引用的都是同一個(gè)對(duì)象。

下圖展示了保存在變量對(duì)象中的變量和保存在堆中的對(duì)象之間的關(guān)系:

1.3、傳遞參數(shù)

??ECMAscript 中所有函數(shù)的參數(shù)都是按值傳遞的。也就是說(shuō),把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù),就和把值從一個(gè)變量復(fù)制到另一個(gè)變量一樣。基本類(lèi)型值的傳遞如同基本類(lèi)型變量的復(fù)制一樣,而引用類(lèi)型值的傳遞,則如同引用類(lèi)型變量的復(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'

??上述例子說(shuō)明即使在函數(shù)內(nèi)部修改了參數(shù)的值,但原始的引用仍保持不變。實(shí)際上,當(dāng)在函數(shù)內(nèi)部重寫(xiě) obj 時(shí),這個(gè)變量引用的就是一個(gè)局部對(duì)象。而這個(gè)局部對(duì)象會(huì)在函數(shù)執(zhí)行完畢后立即被銷(xiāo)毀。

可以把 ECMAscript 函數(shù)的參數(shù)想象成局部變量

1.4、檢測(cè)類(lèi)型

??要檢測(cè)一個(gè)變量是不是基本數(shù)據(jù)類(lèi)型, typeof 操作符是最佳工具。更具體一點(diǎn):typeof 操作符是確定一個(gè)變量是字符串、數(shù)值、布爾值、undefine 的最佳工具。
如果變量是 對(duì)象 或 null, typeof 操作符返回 'object'。

??使用 instanceof 操作符檢測(cè)引用類(lèi)型值是什么類(lèi)型的對(duì)象。
語(yǔ)法:

result = variable instanceof constructor

??如果變量是給定引用類(lèi)型(根據(jù)它的原型鏈來(lái)識(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ī)定:所有引用類(lèi)型的值都是 Object 的實(shí)例。
??因此,在檢測(cè)一個(gè)引用類(lèi)型值和 Object 構(gòu)造函數(shù)時(shí),instanceof 操作符始終都會(huì)返回 true。
??使用 instanceof 操作符檢測(cè)基本類(lèi)型值,始終返回 false。

typeof 操作符檢測(cè)函數(shù)時(shí),會(huì)返回 'function'

2、執(zhí)行環(huán)境及作用域

??執(zhí)行環(huán)境(execution context,有時(shí)也簡(jiǎn)稱(chēng) “ 環(huán)境 ”)是 JavaScript 中最為重要的一個(gè)概念。執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問(wèn)的其他數(shù)據(jù),決定了它們各自的行為。
??每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中,雖然我們編寫(xiě)的代碼無(wú)法訪問(wèn)到這個(gè)對(duì)象,但解析器在處理數(shù)據(jù)時(shí)會(huì)在后臺(tái)使用它。

??全局執(zhí)行環(huán)境是最外圍的一個(gè)執(zhí)行環(huán)境。根據(jù) ECMAscript 實(shí)現(xiàn)所在的宿主環(huán)境不同,表示執(zhí)行環(huán)境的對(duì)象也不一樣。在 Web 瀏覽器中,全局執(zhí)行環(huán)境被認(rèn)為是 window 對(duì)象,因此所有全局變量和函數(shù)都是作為 window 對(duì)象的屬性和方法創(chuàng)建的。某個(gè)執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境被銷(xiāo)毀,保存在其中的所有變量和函數(shù)定義也隨之銷(xiāo)毀(全局執(zhí)行環(huán)境知道應(yīng)用程序退出——例如關(guān)閉網(wǎng)頁(yè)或?yàn)g覽器——時(shí)才會(huì)被銷(xiāo)毀)。

??每個(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)建變量對(duì)象的一個(gè)作用域鏈(scope chain)。作用域鏈的用途,是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)。作用域的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對(duì)象。如果這個(gè)環(huán)境是函數(shù),則將其活動(dòng)對(duì)象(activation Object)作為變量對(duì)象。活動(dòng)對(duì)象在最開(kāi)始是只包含一個(gè)變量,即 arguments 對(duì)象(這個(gè)對(duì)象在全局環(huán)境中是不存在的)。作用域鏈中的下一個(gè)變量對(duì)象來(lái)自包含(外部)環(huán)境,再下一個(gè)變量對(duì)象則來(lái)自下一個(gè)包含環(huán)境。這樣,一直延續(xù)到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對(duì)象始終都是作用域鏈中的最后一個(gè)對(duì)象。

??標(biāo)識(shí)符解析是沿著作用域鏈一級(jí)一級(jí)地搜索標(biāo)識(shí)符的過(guò)程。搜索過(guò)程始終從作用域鏈的前端開(kāi)始,逐級(jí)地向后回溯,直至找到標(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è)對(duì)象:它自己的變量對(duì)象(其中定義著 arguments 對(duì)象)和全局環(huán)境的變量對(duì)象。可以在函數(shù)內(nèi)部訪問(wèn)變量 color ,就是因?yàn)榭梢栽谶@個(gè)作用域鏈中找到它。

??此外,在局部作用域中定義的變量可以在局部環(huán)境中與全局變量互換使用,示例:

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();

??上述代碼共涉及 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ù),但它也可以訪問(wèn)全局環(huán)境中的變量 color。
??swapColors() 的局部環(huán)境中有一個(gè)變量 tempColor,該變量只能在這個(gè)環(huán)境中訪問(wèn)到,無(wú)論全局環(huán)境還是 changeColor() 的局部環(huán)境都無(wú)權(quán)訪問(wèn) tempColor。在 swapColors() 內(nèi)部則可以訪問(wèn)其他兩個(gè)環(huán)境中的所有變量,因?yàn)槟莾蓚€(gè)環(huán)境是它的父執(zhí)行環(huán)境。
??上面例子的作用域鏈:

??上圖中的矩形表示特定的執(zhí)行環(huán)境。內(nèi)部環(huán)境可以通過(guò)作用域鏈訪問(wèn)所有的外部環(huán)境,但外部環(huán)境不能訪問(wèn)到內(nèi)部環(huán)境中的任何變量和函數(shù)。
這些環(huán)境之間的聯(lián)系是線性、有次序的。每個(gè)環(huán)境都可以向上搜索作用域鏈,以查詢(xún)變量和函數(shù)名;任何環(huán)境都不能通過(guò)向下搜索作用域鏈而進(jìn)入另一個(gè)執(zhí)行環(huán)境。

函數(shù)參數(shù)也被當(dāng)作變量來(lái)對(duì)待,因此其訪問(wèn)規(guī)則與執(zhí)行環(huán)境中的其他變量相同。

2.1、延長(zhǎng)作用域鏈

??雖然執(zhí)行環(huán)境的類(lèi)型只有兩種——全局和局部(函數(shù)),但可以延長(zhǎng)作用域鏈。
??有些語(yǔ)句可以作用域鏈的前端臨時(shí)增加一個(gè)變量對(duì)象,該變量會(huì)在代碼執(zhí)行后被移除。
??當(dāng)執(zhí)行流進(jìn)入下列任何一個(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ì)象的聲明。

2.2、查詢(xún)標(biāo)識(shí)符

??當(dāng)在某個(gè)環(huán)境中為了讀取或?qū)懭攵靡粋€(gè)標(biāo)識(shí)符時(shí),必須通過(guò)搜索來(lái)確定該標(biāo)識(shí)符實(shí)際代表什么。搜索過(guò)程沖作用域鏈的前端開(kāi)始,向上逐級(jí)查詢(xún)與給定名字匹配的標(biāo)識(shí)符。如果在局部環(huán)境中找到了該標(biāo)識(shí)符,搜索過(guò)程停止,變量就緒。如果在局部環(huán)境中沒(méi)有找到該變量名,則繼續(xù)沿作用域鏈向上搜索。搜索過(guò)程將一直追溯到全局環(huán)境的變量對(duì)象。如果在全局環(huán)境中也沒(méi)有找到這個(gè)標(biāo)識(shí)符,則說(shuō)明該變量尚未聲明。

??搜索過(guò)程中,如果存在一個(gè)局部的變量的定義,搜索就會(huì)停止,不再進(jìn)入另一個(gè)變量對(duì)象。即,如果局部環(huán)境中存在同名標(biāo)識(shí)符,就不會(huì)使用位于父環(huán)境中的標(biāo)識(shí)符。

3、垃圾收集

??JavaScript 具有自動(dòng)垃圾收集機(jī)制,也就是說(shuō),執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存。所需內(nèi)存的分配以及無(wú)用內(nèi)存的回收完全實(shí)現(xiàn)了自動(dòng)管理。
??垃圾收集機(jī)制原理:找出那些不再繼續(xù)使用的變量,然后釋放其占用的內(nèi)存。
??為此,垃圾收集器會(huì)按照固定的時(shí)間間隔(或代碼執(zhí)行中預(yù)定的收集時(shí)間),周期性的執(zhí)行這一操作。
??函數(shù)中局部變量的正常生命周期:局部變量只在函數(shù)執(zhí)行的過(guò)程中存在。在這個(gè)過(guò)程中,會(huì)為局部變量在棧(或堆)內(nèi)存上分配相應(yīng)的空間,以便存儲(chǔ)它們的值。然后在函數(shù)中使用這些變量,直至函數(shù)執(zhí)行結(jié)束。此時(shí),局部變量就沒(méi)有存在的必要了,因此可以釋放它們的內(nèi)存以共將來(lái)使用。

??為了判斷變量是否有存在的必要,垃圾收集器必須跟蹤哪個(gè)變量有用,哪個(gè)沒(méi)用,對(duì)于不再有用的變量打上標(biāo)記,以備將來(lái)收回其占用的內(nèi)存。

??用于標(biāo)識(shí)無(wú)用變量的策略可能會(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)變量離開(kāi)環(huán)境時(shí),則將其標(biāo)記為 “ 離開(kāi)環(huán)境 ”。

可以使用任何方式來(lái)標(biāo)記變量:

  • 可以通過(guò)反轉(zhuǎn)某個(gè)特殊的位來(lái)記錄一個(gè)變量何時(shí)進(jìn)入環(huán)境
  • 使用一個(gè) “ 進(jìn)入環(huán)境的 ” 變量列表及一個(gè) “ 離開(kāi)環(huán)境的 ” 變量列表來(lái)跟蹤哪個(gè)變量發(fā)生了變化。

??說(shuō)到底,如何標(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)無(wú)法訪問(wèn)到這些變量了。最后,垃圾收集器完成內(nèi)存清除工作,銷(xiāo)毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。

3.2、引用計(jì)數(shù)

??另一種不太常見(jiàn)的垃圾收集策略叫引用計(jì)數(shù)(reference counting)。
??引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值被引用的次數(shù)。當(dāng)聲明了一個(gè)變量并將一個(gè)引用類(lèi)型值賦給該變量時(shí),則這個(gè)值的引用次數(shù)就是 1。如果同一個(gè)值又被賦給另一個(gè)變量,則該值的引用次數(shù)加 1。相反,如果包含對(duì)這個(gè)值引用的變量有取得了另外一個(gè)值,則這個(gè)值的引用次數(shù)減 1,當(dāng)這個(gè)值的引用次數(shù)變成 0 時(shí),則說(shuō)明沒(méi)有辦法在訪問(wèn)到這個(gè)值了,因而就可以將其占用的內(nèi)存空間回收回來(lái)。這樣,當(dāng)垃圾收集器下次再運(yùn)行時(shí),它就會(huì)釋放那些引用次數(shù)為零的值多占用的內(nèi)存。

遇到問(wèn)題:循環(huán)引用

??循環(huán)引用是指對(duì)象 A 中包含一個(gè)指向?qū)ο?B 的指針,而對(duì)象 B 中也包含一個(gè)指向?qū)ο?A 的引用。示例:

function problem() {
    var objectA = new Object();
    var objectB = new Object();

    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}

??在上述例子中,objectA 和 objectB 通過(guò)各自的屬性相互引用;也就是說(shuō),這兩個(gè)對(duì)象的的引用次數(shù)都是 2。在采用標(biāo)記清除策略的實(shí)現(xiàn)中,由于函數(shù)執(zhí)行之后,這兩個(gè)對(duì)象都離開(kāi)了作用域,因此這種相互引用不是個(gè)問(wèn)題。但在采用引用計(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)致大量?jī)?nèi)存得不到回收。

3.3、性能問(wèn)題

??垃圾收集器是周期性運(yùn)行的,而且如果為變量分配的內(nèi)存數(shù)量很可觀,那么回收工作量也是相當(dāng)大的。在這種情況下,確定垃圾收集的時(shí)間間隔是一個(gè)非常重要的問(wèn)題。

在有的瀏覽器中可以觸發(fā)垃圾收集過(guò)程,但不建議這樣做。
在 IE 中,調(diào)用 window.CollectGarbage() 方法會(huì)立即執(zhí)行垃圾收集。
在 Opera 7 及更高版本中,調(diào)用 window.opera.collect() 也會(huì)啟動(dòng)垃圾收集例程。

3.4、管理內(nèi)存

??使用具備垃圾收集機(jī)制的語(yǔ)言編寫(xiě)程序,開(kāi)發(fā)人員一般不必操心內(nèi)存管理問(wèn)題。但是,JavaScript 在進(jìn)行內(nèi)存管理及垃圾收集時(shí)面臨的問(wèn)題還是有點(diǎn)與眾不同。其中最主要的一個(gè)問(wèn)題,就是分配給 Web 瀏覽器的可用內(nèi)存數(shù)量通常要比分配給桌面應(yīng)用程序的少。這樣做的目的主要是出于安全方面的考慮,目的是防止運(yùn)行 JavaScript 的網(wǎng)頁(yè)耗盡全部系統(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ōu)化內(nèi)存占用的最佳方式,就是為執(zhí)行中的代碼只保存必要的數(shù)據(jù)。一旦數(shù)據(jù)不再有用,最好通過(guò)將其值設(shè)置為 null 來(lái)釋放其引用——這個(gè)做法叫做解除引用(dereferencing)。這一做法適用于大多數(shù)全局變量和全局對(duì)象的屬性。局部變量會(huì)在它們離開(kāi)執(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è)對(duì)象并將其賦給局部變量 localPerson,然后又為該對(duì)象添加了一個(gè)名為 name 的屬性。最后,當(dāng)調(diào)用這個(gè)函數(shù)時(shí),localPerson 以函數(shù)值的形式返回并賦給全局變量 globalPerson。由于 localPerson 在 createPerson() 函數(shù)執(zhí)行完畢后就離開(kāi)了其執(zhí)行環(huán)境,因此無(wú)需我們顯示地去為它解除引用。但是對(duì)于全局變量 globalPerson 而言,則需要我們?cè)诓皇褂盟臅r(shí)候手工為它解除引用,這也是上面例子中最后一行代碼的目的。

??不過(guò),解除一個(gè)值的引用并不意味著自動(dòng)回收該值所占用的內(nèi)存。解除引用的真正作用是讓值脫離執(zhí)行環(huán)境,以便垃圾收集器下次運(yùn)行時(shí)將其回收。

小結(jié)

??JavaScript 變量可以用來(lái)保存兩種類(lèi)型的值:基本類(lèi)型值和引用類(lèi)型值。
??基本類(lèi)型值源自以下 5 中基本數(shù)據(jù)類(lèi)型:Undefined、Null、Boolean、Number、String。

基本類(lèi)型值和引用類(lèi)型值具有以下特點(diǎn):

  • 基本類(lèi)型值在內(nèi)存中占據(jù)固定大小的空間,因此被保存在棧內(nèi)存中。
  • 從一個(gè)變量向另一個(gè)變量復(fù)制基本類(lèi)型的值,會(huì)創(chuàng)建這個(gè)值的一個(gè)副本。
  • 引用類(lèi)型的值是對(duì)象,保存在堆內(nèi)存中。
  • 包含引用類(lèi)型值的變量實(shí)際上包含的并不是對(duì)象,而是一個(gè)指向該對(duì)象的指針。
  • 從一個(gè)變量向另一變量復(fù)制引用類(lèi)型的值,復(fù)制的其實(shí)是指針,因此兩個(gè)變量最終都指向同一個(gè)對(duì)象。
  • 確定一個(gè)值是那種基本類(lèi)型可以使用 typeof 操作符,確定一個(gè)值是哪種引用類(lèi)型可以使用 instanceof 操作符。

??所有變量(包括基本類(lèi)型和引用類(lèi)型)都存在于一個(gè)執(zhí)行環(huán)境(也稱(chēng)為作用域)當(dāng)中,這個(gè)執(zhí)行環(huán)境決定了變量的生命周期,以及哪一部分代碼可以訪問(wèn)其中的變量。

執(zhí)行環(huán)境:

  • 執(zhí)行環(huán)境有全局執(zhí)行環(huán)境(也稱(chēng)為全局環(huán)境)和函數(shù)執(zhí)行環(huán)境之分。
  • 每次進(jìn)入一個(gè)新執(zhí)行環(huán)境,都會(huì)創(chuàng)建一個(gè)用于搜索變量和函數(shù)的作用域鏈。
  • 函數(shù)的局部環(huán)境不僅有權(quán)訪問(wèn)函數(shù)作用域中的變量,而且有權(quán)訪問(wèn)其包含(父)環(huán)境,乃至全局環(huán)境。
  • 全局環(huán)境只能訪問(wèn)在全局環(huán)境中定義的變量和函數(shù),而不能之間訪問(wèn)局部環(huán)境中的任何數(shù)據(jù)。
  • 變量的執(zhí)行環(huán)境有助于確定應(yīng)該何時(shí)釋放內(nèi)存。

??JavaScript 是一門(mén)具有自動(dòng)垃圾收集機(jī)制的編程語(yǔ)言,開(kāi)發(fā)人員不必關(guān)心內(nèi)存分配和回收問(wèn)題。可以對(duì) JavaScript 的垃圾收集例程做如下總結(jié):

  • 離開(kāi)作用域的值將被自動(dòng)標(biāo)記為可以回收,因此將在垃圾收集期間被刪除。
  • “ 標(biāo)記清除 ” 是目前主流的垃圾收集算法,這種算法的思想是給當(dāng)前不使用的值加上標(biāo)記,然后再回收其內(nèi)存。
  • 另一種垃圾收集算法是 “ 引用計(jì)數(shù) ”,這種算法的思想是跟蹤記錄所有值被引用的次數(shù)。JavaScript 引擎目前都不再使用這種算法;但在 IE 訪問(wèn)非原生 JavaScript 對(duì)象(IE9 以下,如 DOM 元素)時(shí),這種算法仍然可能會(huì)導(dǎo)致問(wèn)題。
  • 當(dāng)代碼中存在循環(huán)引用現(xiàn)象時(shí),“ 引用計(jì)數(shù) ” 算法就會(huì)導(dǎo)致問(wèn)題。
  • 解除變量的引用不僅有助于消除循環(huán)引用現(xiàn)象,而且對(duì)垃圾收集也有好處。為了確保有效地回收內(nèi)存,應(yīng)該及時(shí)解除不再使用的全局對(duì)象、全局對(duì)象屬性以及循環(huán)引用變量的引用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 本章內(nèi)容 理解基本類(lèi)型和引用類(lèi)型的值 理解執(zhí)行環(huán)境 理解垃圾收集 由于不存在定義某個(gè)變量必須要保存何種數(shù)據(jù)類(lèi)型值的...
    悶油瓶小張閱讀 319評(píng)論 0 0
  • 1.1 基本類(lèi)型和引用類(lèi)型的值 ECMAScript變量可能包含兩種不同數(shù)據(jù)類(lèi)型的值:基本類(lèi)型值和引用類(lèi)型值。基本...
    Jackandjohn閱讀 306評(píng)論 0 0
  • 第2章 基本語(yǔ)法 2.1 概述 基本句法和變量 語(yǔ)句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,195評(píng)論 0 13
  • 周末和老公一起外出辦事,回來(lái)的路上,他專(zhuān)心開(kāi)車(chē),我安安靜靜地坐在后座看雜志。手機(jī)提示來(lái)了一條消息,我像平常一...
    張古古閱讀 626評(píng)論 0 0
  • 小時(shí)候很喜歡讀書(shū),只是家里有的,多是連環(huán)畫(huà)之類(lèi)的,翻來(lái)復(fù)去的看了幾遍,就有了不足之心。 家里生活雖是拮據(jù),但母親見(jiàn)...
    龍少之說(shuō)閱讀 385評(píng)論 3 3