1. 程式人生 > >《JavaScript 高級程序設計》總計四(2)

《JavaScript 高級程序設計》總計四(2)

占用 strong 結果 scrip 垃圾收集器 全局環境 代碼 垃圾回收 返回

引言:這一節我們對執行環境及作用域以及JavaScript的內存、垃圾機制等進行總結。

執行環境:執行環境(或者直接稱:環境)是JavaScript 中最為重要的一個概念,執行環境定義了變量或函數有權訪問的其他數據,決定了他們的各自行為。每個執行環境都有一個與之關聯的 變量對象 ,環境中定義的所有變量和函數都保存咋這個對象中。雖然我們編寫的代碼無法訪問這個對象,但是解析器在處理數據時會在後臺使用它。

全局執行環境是最外層的一個環境,在WEB瀏覽器中,全局執行環境被認為是 window 對象,因此也可以認為所有全局變量和函數都是作為 window 對象的屬性和方法創建的。某個執行環境中所有代碼執行完畢後該環境銷毀,保存在其中的所有變量和函數定義也隨之銷毀(全局執行環境直到應用程序退出——例如關閉網頁或瀏覽器時才會被銷毀)。

作用域鏈:當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈,作用域鏈的作用就是保證執行環境有權訪問的所有變量和函數的有序訪問。作用域鏈的前端始終是當前執行的代碼所在環境的變量對象。如果這個環境是函數,則將其活動對象作為變量對象。活動對象在最開始只包含一個變量,即 arguments 對象(這個對象在全局環境中是不存在的)。在作用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個對象則來自下一個包含環境,這樣一直延續到全局環境,全局執行環境的變量對象始終是作用域鏈中的最後一個對象。這句話有些繞難以理解,我們直接看例子:

                            

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

以上涉及三個執行環境,即全局變量環境、changeColor() 的局部變量環境,還有 swapColors()的局部變量環境。而從上面的例子也能夠看出。這個執行的的作用域鏈是由內部到外部的也就是上面提到的“用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個對象則來自下一個包含環境,這樣一直延續到全局環境”。通過這樣的方式,內部函數可以使用外部函數的變量以及全局的變量,但是反過來,我們在外部不能直接訪問到內部函數的變量。這之後也就出現了閉包(之後我們會總結)

延長作用域鏈 :當執行函數流入下列任何一個語句時,作用域鏈就會得到加長:1、try—catch 語句的 catch 塊 2、with語句。

舉個栗子:

function buildUrl(){
var qs="?debug=true"
with(location){
var url =href+qs;
}
return url;
}

                                    

在這裏 with 接收的是location 對象,因此其變量中就包含了location 對象的所有屬性和方法,而這個對象唄添加到了作用域前端。因此在這裏我們就可以引用到 變量qs 。至於with 語句內部,則定義了一個url 變量,所以 url 就成了函數執行環境的一部分,所以就可以作為函數的值被返回。

ps (在IE8及其之前的版本,cath 的錯誤對象會被添加到執行環境中而不是在catch 的環境中,所以會出現在IE8 及其之前的版本中可以在catch 外部訪問錯誤對象的情況,這種問題在IE9 以及得以修復)。

垃圾回收機制:

JavaScript 具有垃圾回收機制,也就是說,執行環境會負責管理代碼執行過程使用的內存。這種垃圾回收機制實現原理很簡單:找出那些不用的變量,然後釋放其占用的資源。為此,垃圾收集器會按照固定時間間隔(或代碼執行中預定的收集時間周期性的執行這一操作)。

局部變量的正常生命周期:局部變量只在函數執行的過程中存在,而在這個過程中,會為局部變量在棧(或堆)上分配相應的空間,以便儲存它的值。然後在函數中使用這些變量,直至函數結束,此時,局部變量就沒有存在的必要了,因此可以釋放他們的內存。在這種情況下很容易判斷變量是否有存在的必要,但是,並非所有情況都那麽容易得出結論。垃圾收集器必須跟蹤哪個變量有用哪個沒用。對於不在有用的變量打上標簽,以便後面釋放其資源。用於標識無用變量的策略可能會因時實而異,但是具體到瀏覽器中實現,則有兩種情況:

1、標記清除: 這是JavaScript最常見的垃圾回收方式,當變量進入執行環境的時候,比如函數中聲明一個變量,垃圾回收器將其標記為“進入環境”,當變量離開環境的時候(函數執行結束)將其標記為“離開環境”。至於怎麽標記有很多種方式,比如特殊位的反轉、維護一個列表等,這些並不重要,重

要的是使用什麽策略,原則上講不能夠釋放進入環境的變量所占的內存,它們隨時可能會被調用的到。

   垃圾回收器會在運行的時候給存儲在內存中的所有變量加上標記,然後去掉環境中的變量以及被環境中變量所引用的變量(閉包),在這些完成之後仍存在標記的就是要刪除的變量了,因為環境中的變量已經無法訪問到這些變量了,然後垃圾回收器相會這些帶有標記的變量機器所占空間。

大部分瀏覽器都是使用這種方式進行垃圾回收,區別在於如何標記及垃圾回收間隔而已,只有低版本IE,不出所料,又是IE。。。

2、引用計數:另一種不太常見的垃圾回收策略是引用計數。引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量並將一個引用類型賦值給該變量時,則這個值的引用次數就是1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數就減1。當這個引用次數變成0

時,則說明沒有辦法再訪問這個值了,因而就可以將其所占的內存空間給收回來。這樣,垃圾收集器下次再運行時,它就會釋放那些引用次數為0的值所占的內存。

  引用計數這個方式很容易理解,但是在使用時會碰上一個問題,那就是循環引用:

    function pro(){

    var a=new Object();

    var b=new Object();

    a.Obj=b;

    b.obj=a;

} 在這個函數中,a、b通過各自的屬性相互引用。也就是說二者的引用次數都是2。 這就很尷尬了,當函數執行完畢後,二者的引用次數都不是0,那麽這時候引用計數的方式就不行了。如果這個方法被執行多次,那麽裏面的變量就會堆積,這不是我們想要看到的結果。

    

 我們知道,IE中有一部分對象並不是原生JavaScript對象。例如,其BOM和DOM中的對象就是使用C++以COM(Component Object
Model,組件對象)對象的形式實現的,而COM對象的垃圾回收器就是采用的引用計數的策略。因此,即使IE的Javascript引擎使用標記清除的策略來實現的,但JavaScript訪問的COM對象依然是基於引用計數的策略的。說白了,只要IE中涉及COM對象,就會存在循環引用的問題。看看下面的這個簡單的例子:

var element = document.getElementById("some_element");
var myObj =new Object();
myObj.element = element;
element.someObject = myObj;

  上面這個例子中,在一個DOM元素(element)與一個原生JavaScript對象(myObj)之間建立了循環引用。其中,變量myObj有一個名為element的屬性指向element;而變量element有一個名為someObject的屬性回指到myObj。由於循環引用,即使將例子中的DOM從頁面中移除,內存也永遠不會回收。

  不過上面的問題也不是不能解決,我們可以手動切斷他們的循環引用。

myObj.element = null;
element.someObject =null;

  這樣寫代碼的話就可以解決循環引用的問題了,也就防止了內存泄露的問題。

  不過為了解決上述問題,IE9 把BOM 和DOM 對象都轉成真正的JavaScript對象。這樣就避免了兩種垃圾回收算法並存導致的問題。也就防止了內存泄露的問題。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

本章完。下一章預告:

引用類型。

我們知道JavaScript中有兩種類型,數據類型和引用類型。前面的總結已經讓我們對基礎數據類型有了了解,接下來讓我們走進引用類型的世界。

《JavaScript 高級程序設計》總計四(2)