【JS基礎(chǔ)】(六)JavaScript變量、作用域和內(nèi)存問題

對(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ù)想象成局部變量。
  1. 在向參數(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的值。

  1. 在向參數(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)系:

引用類型參數(shù)傳遞-1

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

引用類型參數(shù)傳遞-2

以上代碼中創(chuàng)建一個(gè)對(duì)象,并將其保存在變量person中。然后,這個(gè)變量被傳遞到setName(obj)函數(shù)中之后就被復(fù)制給了obj。在這個(gè)函數(shù)內(nèi)部,objperson引用的是同一個(gè)對(duì)象。于是,在函數(shù)內(nèi)部為obj添加name屬性后,函數(shù)外部的person也將有所反應(yīng);因?yàn)檫@時(shí)的personobj指向同一個(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í)personobj的關(guān)系圖:

引用類型參數(shù)傳遞-3

這個(gè)例子與前一個(gè)唯一的區(qū)別,就是setName()函數(shù)中添加了兩行代碼: obj = new Object(); 用來改變obj的指向; obj.name = "Greg";用來給新創(chuàng)建的obj添加屬性。如果是按引用傳遞的,那么person就會(huì)自動(dòng)被修改為指向新創(chuàng)建的obj的內(nèi)存地址,則personname屬性值被修改為"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)境及作用域

  1. 執(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)境中的其他變量相同。
  1. 沒有塊級(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)引用變量的引用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容