《高級程序設計》 4 變量、做用域和內存問題

一、基本類型值和引用類型值javascript

javascript變量包含兩種不一樣數據類型的值:基本類型值和引用類型值。基本類型值指的是簡單的數據字段,而引用類型值指那些可能由多個值構成的對象。html

5種基本數據類型:(Undefined,Null,Boolean,Number,String)是按值訪問的,由於能夠操做保存在變量中的實際的值。前端

引用類型的值是保存在內存中的對象。javascript不能直接訪問內存中的位置,也就是不能直接操做對象的內存空間。在操做對象時,其實是在操做對象的引用而不是實際的對象。爲此,引用類型的值是按引用訪問的。java

注:在不少語言中,字符串以對象的形式來表示,所以被認爲是引用類型的。javascript放棄了這一傳統。web

1)動態的屬性正則表達式

咱們能夠爲引用類型添加屬性,並訪問這個新屬性,可是不能給基本類型的值添加屬性。算法

var person=new Object();
person.name="Nicholas";
alert(person.name);  //"Nicholas"
//not
var name="Nicholas";
name.age=27;
alert(name.age); //undefined
//    只能給引用類型值動態地添加屬性,以便未來使用

2)複製變量值編程

除了保存方式不一樣,在從一個變量向另外一個變量複製基本類型值和引用類型值時,也存在不一樣。瀏覽器

var num1 = 5;
var num2 = num1;
alert(num2);  //5
//    若是從一個變量向另外一個變量複製基本類型的值,會在變量對象上建立一個新值,而後把該值複製到爲新變量分配的位置上。
//    num2中的5與num1中的5是徹底獨立的。這兩個變量能夠參與任何操做而不會相互影響。
var obj1=new Object();
var obj2=obj1;
obj1.name="Nicholas";
alert(obj2.name);  //"Nicholas"
//    當一個變量向另外一個變量複製引用類型的值時,一樣也會將存儲在變量對象中的值複製一份放到爲新的變量分配的空間中。不一樣的是,這個值的副本
//    其實是一個指針,而這個指針指向存儲在堆中的一個對象。兩個變量實際將引用同一個對象。所以,改變其中一個變量,就會影響另外一個變量。

3)傳遞參數編程語言

javascript中全部函數的參數都是按值傳遞的。基本類型值的傳遞如同基本類型變量的複製同樣,引用類型值的傳遞如同引用類型變量的複製同樣。

在向參數傳遞基本類型的值時,被傳遞的值會被複制給一個局部變量(即命名參數,或者用javascript的概念來講,就是arguments對象中的一個元素)。

在向參數傳遞引用類型的值時,會把這個值在內存中的地址複製給一個局部變量,所以這個局部變量的變化會反映在函數的外部。

    //    基本類型
    function addTen(num) {
        num += 10;
        return num;
    }
    var count = 20;
    var result = addTen(count);
    alert(count); //20
    alert(result); //30
    //    引用類型
    function setName(obj) {
        obj.name = "Nicholas";
    }
    var person = new Object();
    person.name = "Greg";
    alert(person.name); //"Greg"
    setName(person);
    alert(person.name); //"Nicholas"
//    在函數內部,obj和person引用的是同一個對象。
//    不少開發人員錯誤的認爲:在局部做用域中修改的對象會在全局做用域中反映出來,就說明參數是按引用傳遞的,
// 爲了證實對象是按值傳遞的,對比:
    function setName1(obj){
        obj.name="Nicholas";
        obj=new Object();
        obj.name="Greg";
    }
    var person1=new Object();
    setName1(person1);
    alert(person1.name); //Nicholas
//    當在函數內部重寫obj時,這個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢後當即被銷燬。

注意:能夠把javascript函數的參數想象成局部變量。

4)檢測類型

當要檢測一個變量是否是基本數據類型時,使用typeof操做符是最佳的工具。

    //    typeof操做符
//    var s = "Nicholas";
//    var b = true;
//    var i = 22;
//    var u;
//    var n = null;
//    var o = new Object();
//    alert(typeof s); //string
//    alert(typeof b); //boolean
//    alert(typeof i); //number
//    alert(typeof u); //undefined
//    alert(typeof n); //object
//    alert(typeof o); //object
    //    當咱們並非想知道某個值是對象,而是想知道它是什麼類型的對象。使用:
    //    instanceof 操做符
    var person = new Object();
    var array = new Array();
    var pattern = new RegExp();
    alert(person instanceof Object); //true
    alert(array instanceof Array); //true
    alert(array instanceof Object); //true .全部引用類型的值都是Object的實例,所以在檢測一個引用類型值和Object構造函數時,
//    instanceof操做符始終會返還true
    alert(pattern instanceof RegExp); //true

使用typeof操做符檢測函數時,該操做符會返回「function」。

在ie和firefox中,對正則表達式應用typeof會返回「object「

二、執行環境及做用域

執行環境定義了變量或函數有權訪問的其餘數據,決定了了它們各自的行爲。

全局執行環境是最外圍的一個執行環境。在web瀏覽器中,全局執行環境被認爲是window對象,所以全部全局變量和函數都是做爲window對象的屬性和方法建立的。某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保持在其中的全部變量和函數定義也隨之銷燬(全局執行環境直到應用程序退出——例如關閉網頁或瀏覽器時纔會被銷燬)。

當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈。做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象做爲變量的對象。活動對象在最開始只包含一個變量,即arguments對象(這個對象在全局環境中是不存在的)。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。

標識符的解析是沿着做用域鏈一級一級地搜索標識符的過程。搜索過程始終從做用域鏈的前端開始,而後逐級地向後回溯,直到找到標識符爲止(若是找不到標識符,一般會致使錯誤發生),示例:

//    var color = "blue";
//    function changeColor() {
//        if (color == "blue") {
//            color = "red";
//        } else {
//            color = "blue";
//        }
//    }
//    changeColor();
//    alert("Color is now " + color);  //"Color is now red"
//    函數changeColor()的做用域鏈包含鏈各個對象:它本身的變量對象(其中定義着arguments對象)和全局環境的變量對象。
// 能夠在函數內部訪問變量color,就是由於能夠在這個做用域鏈中找到它。
//    此外,在局部做用域中定義的變量能夠在局部環境中與全局變量互換使用,以下:
    var color="blue";
    function changeColor(){
        var anotherColor="red";
        function swapColors(){
            var tempColor=anotherColor;
            anotherColor=color;
            color=tempColor;
//            這裏能夠訪問color,anotherColor,tempColor
        }
//        這裏能夠訪問color和anotherColor,但不能訪問tempColor
        swapColors();
        alert(anotherColor); //"blue"
    }
//    這裏只能訪問color
    changeColor();
    alert("Color is now "+color); //"Color is now red"

注意:函數參數也被看成變量來對待,所以其訪問規則與其執行環境中的其它變量相同

1)延長做用域

雖然執行環境的類型總共只有兩種——全局和局部(函數),但仍是有其它辦法來延長做用域鏈。這麼說是由於有些語句能夠在做用域鏈的前端臨時增長一個變量對象,該變量對象會在代碼執行後被移除。在兩種狀況下會發生這種現象,具體來講,就是當執行流進入下列任何一個語句時,做用域鏈就會獲得加長:

  • try-catch語句的catch塊;
  • with語句。
    function buildUrl() {
        var qs = "?debug=true";
        with (location) {
            var url = href + qs;
        }
        return url;
    }
    alert(buildUrl()); //"http://localhost:63342/MYIDEA/index.html?debug=true"

2)沒有塊級做用域

在其餘類C的語言中,有花括號封閉的代碼塊都有本身的做用域(若是用javascript的話來說,就是它們本身的執行環境)。可是,javascript沒有塊級做用域。

    if(true){
        var color="blue";
    }
    alert(color); //"blue"
//    if語句中的變量聲明會將變量添加到當前執行環境(在這裏是全局環境)中。
    for(var i=0;i<10;i++){
//        doSomething();
    }
    alert(i);  //10
//    for語句建立的變量i即便在for循環執行結束後,也依舊會存在於循環外部的執行環境中。

聲明變量

注意:在函數內部,若是初始化變量沒有使用var聲明,該變量會自動被添加到全局環境中。建議在初始化變量以前,必定要先聲明。

查詢標識符

當在某個環境中爲了讀取或寫入而引用一個標識符時,必須經過搜索來肯定該標識符實際表明什麼。搜索過程從做用域鏈的前端開始,向上逐級查詢與給定名字匹配的標識符。若是在局部環境中找到該標識符,搜索過程中止,變量就緒。若是在局部環境中沒有找到該變量名,則繼續沿做用域鏈向上搜索。搜索過程將一直追溯到全局環境的變量對象。若是在全局環境中也沒有找到這個標識符,則意味着該變量還沒有聲明。

若是局部環境中存在着同名標識符,就不會使用位於父環境中的標識符。

//    例1
//    var color = "blue";
//    function getColor() {
//        return color;
//    }
//    alert(getColor()); //"blue"
//    例2
    var color="blue";
    function getColor(){
        var color="red";
        return color;
    }
    alert(getColor()); //"red"
//    任何位於局部變量color的聲明以後的代碼,若是不使用window.color都沒法訪問全局變量color。

變量查詢也不是沒有代價的。很明顯,訪問局部變量要比訪問全局變量更快,由於不用向上搜索做用域鏈。javascript引擎在優化標識符查詢方面作得不錯,所以這個差異在未來恐怕就能夠忽略不計了。

三、垃圾收集

 javascript具備自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行過程當中使用的內存。所需內存的分配以及無用內存的回收徹底實現了自動管理。

原理:找出那些再也不繼續使用的變量,而後釋放其佔用的內存。爲此,垃圾收集器會按照固定的時間間隔(或代碼執行中預約的收集時間)週期性地執行這一操做。

確保佔用最少的內存可讓頁面得到更好的性能。而優化內存佔用的最佳方式,就是爲執行中的代碼只保存必要的數據。

 一旦數據再也不有用,最好經過將其值設置爲null來釋放其引用——這個作法叫作解除引用。這一作法適用於大多數全局變量和全局對象的屬性。局部變量會在它們離開執行環境時自動被解除引用。

不過,解除一個值的引用並不意味着自動回收該值所佔用的內存。解除引用的真正做用是讓值脫離執行環境,以便垃圾收集器下次運行時將其回收。

小結:

javascript變量能夠用來保存兩種類型的值:基本類型值和引用類型值。基本類型的值源自如下5中基本數據類型:Undefined,Null,Boolean,Number和String。基本類型值和引用類型值具備如下特色:

  • 基本類型值在內存中佔據固定大小的空間,所以被保存在棧內存中;
  • 從一個變量向另外一個變量複製基本類型的值,會建立這個值的一個副本;
  • 引用類型的值是對象,保存在堆內存中;
  • 包含引用類型值的變量實際上包含的並非對象自己,而是一個指向該對象的指針;
  • 從一個變量向另外一個變量複製引用類型的值,複製的實際上是指針,所以兩個變量最終都指向同一個對象。
  • 肯定一個值是哪一種基本類型可使用typeof操做符,而肯定一個值是哪一種引用類型可使用instanceof操做符。

全部變量(包括基本類型和引用類型)都存在於一個執行環境中(也稱爲做用域)當中,這個執行環境決定了變量的生命週期,以及哪一部分代碼能夠訪問其中的變量。如下是關於執行環境的幾點總結:

  • 執行環境有全局執行環境(也稱爲全局環境)和函數執行環境之分;
  • 每次進入一個新執行環境,都會建立一個用於搜索變量和函數的做用域鏈;
  • 函數的局部環境不只有權訪問函數做用域中的變量,並且有權訪問其包含(父)環境,乃至全局環境;
  • 全局環境只能訪問在全局環境中定義的變量和函數,而不能直接訪問局部環境中的任何數據;
  • 變量的執行環境有助於肯定應該什麼時候釋放內存。

javascript是一門具備自動垃圾收集機制的編程語言,開發人員沒必要關心內存分配和回收問題。以對javascript的垃圾收集例程做以下總結:

  • 離開做用域的值將被自動標記爲能夠回收,所以將在垃圾收集期間被刪除;
  • 」標記清除「是目前主流的垃圾收集算法,這種算法的思想是給當前不適用的值加上標記,而後再回收其內存;
  • 另外一種垃圾收集算法是」引用計數「,這種算法的思想是跟蹤記錄全部值被引用的次數。javascript引擎目前都再也不使用這種算法;但在ie中訪問非原生javascript對象(如DOM元素)時,這種算法仍然可能會致使問題。
  • 當代碼中存在循環引用現象時,」引用計數「算法就會致使問題。
  • 解除變量的引用不只有助於消除循環引用現象,並且對垃圾收集也有好處。爲了確保有效地回收內存,應該及時解除再也不使用的全局對象、全局對象屬性以及循環引用變量的引用。
相關文章
相關標籤/搜索