對(duì)基本類型和引用類型的值的內(nèi)存空間、按值訪問或按引用訪問、以及變量值的復(fù)制可參考《【JS基礎(chǔ)進(jìn)階】JavaScript棧內(nèi)存與堆內(nèi)存》。
(一)函數(shù)參數(shù)的傳遞
ECMAScript 中所有函數(shù)的參數(shù)都是按值傳遞的。也就是說,把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù),就和把值從一個(gè)變量復(fù)制到另一個(gè)變量一樣。
- 基本類型值的傳遞如同基本類型變量的復(fù)制一樣;
- 引用類型值的傳遞,則如同引用類型變量的復(fù)制一樣。
- 訪問變量有按值和按引用兩種方式,而參數(shù)只能按值傳遞;
- 可以把
ECMAScript函數(shù)的參數(shù)想象成局部變量。
- 在向參數(shù)傳遞基本類型的值時(shí),被傳遞的值會(huì)被復(fù)制給一個(gè)局部變量,這個(gè)局部變量的變化不會(huì)影響函數(shù)的外部。
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,沒有變化
alert(result); //30
變量count的值被傳遞給函數(shù)的參數(shù)num以便在函數(shù)中使用,此時(shí)變量count和參數(shù)num的值雖然是一樣的,但是它們是兩個(gè)相互獨(dú)立的變量,在函數(shù)中改變參數(shù)num的值并不會(huì)影響函數(shù)外部的變量count的值。
- 在向參數(shù)傳遞引用類型的值時(shí),會(huì)把 這個(gè)值在內(nèi)存中的地址復(fù)制給一個(gè)局部變量,因此這個(gè)局部變量的變化會(huì)反映在函數(shù)的外部。
function setName(obj) {
obj.name = "Nicholas";
}
var person = new Object();
setName(person); // obj = person
alert(person.name);
當(dāng) var person = new Object(); 時(shí),可以用下圖表示變量和對(duì)象的關(guān)系:

當(dāng)調(diào)用函數(shù) setName(person); 時(shí),下圖可以表示全局變量person和局部變量obj的關(guān)系:

以上代碼中創(chuàng)建一個(gè)對(duì)象,并將其保存在變量person中。然后,這個(gè)變量被傳遞到setName(obj)函數(shù)中之后就被復(fù)制給了obj。在這個(gè)函數(shù)內(nèi)部,obj和person引用的是同一個(gè)對(duì)象。于是,在函數(shù)內(nèi)部為obj添加name屬性后,函數(shù)外部的person也將有所反應(yīng);因?yàn)檫@時(shí)的person和obj指向同一個(gè)堆內(nèi)存地址。所以,很多人錯(cuò)誤的認(rèn)為:在局部作用域中修改的對(duì)象會(huì)在全局對(duì)象中反映出來,就說明參數(shù)是按引用傳遞的。
為了證明對(duì)象也是按值傳遞的,我們?cè)賮砜纯聪旅孢@個(gè)經(jīng)過修改的例子:
function setName(obj) {
obj.name = "Nicholas";
//改變obj的指向,此時(shí)obj指向一個(gè)新的內(nèi)存地址,不再和person指向同一個(gè)
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
當(dāng)創(chuàng)建obj對(duì)象 obj = new Object(); 時(shí),來看看這時(shí)person和obj的關(guān)系圖:

這個(gè)例子與前一個(gè)唯一的區(qū)別,就是setName()函數(shù)中添加了兩行代碼: obj = new Object(); 用來改變obj的指向; obj.name = "Greg";用來給新創(chuàng)建的obj添加屬性。如果是按引用傳遞的,那么person就會(huì)自動(dòng)被修改為指向新創(chuàng)建的obj的內(nèi)存地址,則person的name屬性值被修改為"Greg"。但是,當(dāng)訪問person.name時(shí),顯示的結(jié)果為"Nicholas"。這說明即使在函數(shù)內(nèi)部修改了參數(shù)的值,但原始的引用仍然保持未變。實(shí)際上,當(dāng)在函數(shù)內(nèi)部重寫obj時(shí),這個(gè)變量引用的就是一個(gè)局部對(duì)象了。而這個(gè)局部對(duì)象會(huì)在函數(shù)執(zhí)行完畢后被立即銷毀!
(二)執(zhí)行環(huán)境及作用域
- 執(zhí)行環(huán)境概念
執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了它們各自的行為。每個(gè)執(zhí)行環(huán)境都有一個(gè) 與之關(guān)聯(lián)的變量對(duì)象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中。
每個(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)訪問的所有變量和函數(shù)的有序訪問。
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();
關(guān)于執(zhí)行環(huán)境的幾 點(diǎn)總結(jié):
- 執(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)存;
- 函數(shù)參數(shù)也被當(dāng)作變量來對(duì)待,因此其訪問規(guī)則與執(zhí)行環(huán)境中的其他變量相同。
- 沒有塊級(jí)作用域
for (var i=0; i < 10; i++){
doSomething(i);
}
alert(i); //10
對(duì)于有塊級(jí)作用域的語言來說,for 語句初始化變量的表達(dá)式所定義的變量,只會(huì)存在于循環(huán)的環(huán) 境之中。而對(duì)于 JavaScript 來說,由 for 語句創(chuàng)建的變量 i 即使在 for 循環(huán)執(zhí)行結(jié)束后,也依舊會(huì)存在 于循環(huán)外部的執(zhí)行環(huán)境中。
(1)聲明變量
- 使用
var聲明的變量會(huì)自動(dòng)被添加到最接近的環(huán)境中。在函數(shù)內(nèi)部,最接近的環(huán)境就是函數(shù)的局部環(huán)境;在with語句中,最接近的環(huán)境是函數(shù)環(huán)境。 - 如果初始化變量時(shí)沒有使用
var聲明,該變量會(huì)自 動(dòng)被添加到全局環(huán)境。
function add(num1, num2) {
sum = num1 + num2;
return sum; }
var result = add(10, 20); //30 alert(sum);
(2)查詢標(biāo)識(shí)符
- 當(dāng)在某個(gè)環(huán)境中為了讀取或?qū)懭攵靡粋€(gè)標(biāo)識(shí)符時(shí),必須通過搜索來確定該標(biāo)識(shí)符實(shí)際代表什 么。搜索過程從作用域鏈的前端開始,向上逐級(jí)查詢與給定名字匹配的標(biāo)識(shí)符。
- 如果在局部環(huán)境中找到 了該標(biāo)識(shí)符,搜索過程停止,變量就緒。
- 如果在局部環(huán)境中沒有找到該變量名,則繼續(xù)沿作用域鏈向上 搜索。搜索過程將一直追溯到全局環(huán)境的變量對(duì)象。
- 如果在全局環(huán)境中也沒有找到這個(gè)標(biāo)識(shí)符,則意味 著該變量尚未聲明。
var color = "blue";
function getColor(){
var color = "red";
return color;
}
alert(getColor()); //"red"
(三)垃圾收集
JavaScript 是一門具有自動(dòng)垃圾收集機(jī)制的編程語言,開發(fā)人員不必關(guān)心內(nèi)存分配和回收問題。
關(guān)于JavaScript 的垃圾收集的一些總結(jié):
- 離開作用域的值將被自動(dòng)標(biāo)記為可以回收,因此將在垃圾收集期間被刪除。
- “標(biāo)記清除”是目前主流的垃圾收集算法,這種算法的思想是給當(dāng)前不使用的值加上標(biāo)記,然 后再回收其內(nèi)存。
- 另一種垃圾收集算法是“引用計(jì)數(shù)”,這種算法的思想是跟蹤記錄所有值被引用的次數(shù)。
JavaScript引擎目前都不再使用這種算法;但在IE中訪問非原生JavaScript對(duì)象(如DOM元素)時(shí),這種 算法仍然可能會(huì)導(dǎo)致問題。- 當(dāng)代碼中存在循環(huán)引用現(xiàn)象時(shí),“引用計(jì)數(shù)”算法就會(huì)導(dǎo)致問題。
- 解除變量的引用不僅有助于消除循環(huán)引用現(xiàn)象,而且對(duì)垃圾收集也有好處。為了確保有效地回 收內(nèi)存,應(yīng)該及時(shí)解除不再使用的全局對(duì)象、全局對(duì)象屬性以及循環(huán)引用變量的引用。