《JavaScript高級程序設計》(第3版)讀書筆記 第4章 變量、做用域和內存問題

基本類型和引用類型的值

  • ECMAscript變量包含 基本類型值和引用類型值
  • 基本類型值值的是基本數據類型:Undefined, Null, Boolean, Number, String
  • 引用類型值是保存在內存空間中的對象,與其餘JavaScript不容許直接訪問內存中的位置,即不能直接操做對象的內存空間,在操做對象時,其實是在操做對象的引用而不是實際的對象;爲此,引用類型的值是按引用訪問的
  • 在不少語言中,字符串以對象 的形式來表示,所以被認爲是引用類型的,ECMAScript放棄了這一傳統

動態的屬性

  • 引用類型的值能夠爲其添加屬性和方法,也能夠改變和刪除屬性和方法
  • 基本類型值不行
var person = new Object();
person.name = "Nicholas";
alert(person.name);        // "Nicholas"

var person = "Nicholas";
person.age = 27;
alert(person.age);        // undefined

複製變量值

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

傳遞參數

  • ECMAscript中全部函數的參數都是按值傳遞的,即把函數外部的值複製給函數內部的參數,就和把值從一個變量複製到另外一個變量同樣
  • 在向參數傳遞基本類型的值時,被傳遞的值會被複制給一個局部變量(即爲命名參數,或者用ECMAScript的概念來講,就是函數的arguments對象中的一個元素)
  • 在向參數傳遞引用類型的值時,會把這個值在內存中的地址複製給一個局部變量,所以這個局部變量的變化會反映在函數外部
  • 能夠把ECMAScript的參數想象成局部變量
// 參數num是函數的局部變量
// 調用這個函數時,變量count做爲參數被傳遞給函數,是將count的值20複製給參數num
// 在函數內部,參數num的值加上了10,但不會影響函數外部的count變量,count和num互不影響
function addTen(num) {
    num += 10;
    return num;
}
var count = 20;
var result = addTen(count);
alert(count);        // 20, 沒有變化
alert(result);       // 30
// 如下爲我的理解,原著表述不清楚不能直觀理解
// 傳遞給參數obj的其實是指向person對象內存地址(指針),是將地址複製了一個副本賦值給了參數obj
// 當在函數內部爲obj添加name屬性後,函數內部根據地址操做了內存中的相應對象,函數外部的person也將有反映
// 由於person指向的對象在堆內存中只有一個,並且是全局對象
function setName(obj) {
    obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name);        // "Nicholas"

// 下列代碼,直覺認爲會alert出"Greg",但倒是"Nicholas"
// 由於傳遞給參數obj的只是指針的副本,當函數內從新給obj賦值的時候,其實是將一個新的Object對象的指針賦給obj
// 此時操做obj影響的是新的Object對象,而person對象不受影響
// obj指向的新Object對象在函數執行完後會當即銷燬
function setName(obj) {
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name);        // "Nicholas"

檢測類型

  • typeof操做符是肯定一個變量是否是基本數據類型的最佳工具
  • null和對象 typeof都會返回"object"
  • 檢測引用類型的值,instanceof操做符是最佳工具
  • 根據規定,全部引用類型的值都是Object的實例。所以在檢測一個引用類型值和Object構造函數時,instanceof操做符始終返回true。若是使用instanceof操做符檢測基本類型的值,返回false,由於基本類型不是對象
  • typeof 檢測函數會返回"function".在Safari 5 及 以前版本 Chrome 7 及 以前版本中使用typeof檢測正則表達式,也返回"function",ECMA-262規定任何在內部實現[[call]]方法的對象都應該在應用typeof操做符時返回"function",因爲上述瀏覽器中的正則表達式也實現了這個方法,所以對正則表達式應用typeof會返回"function"。其餘瀏覽器返回"object".

執行環境及做用域

  • 執行環境(execution context,爲簡單起見,有時也成爲「環境」)是JavaScript中最爲重要的一個概念
  • 每一個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的全部變量和函數都保存在這個對象中。
  • 雖然編寫的代碼沒法訪問這個對象,但解析器在處理數據時會在後臺使用它
  • 全局執行環境是最外圍的一個執行環境。在web瀏覽器中,全局執行環境被認爲是window對象,所以全部全局變量和函數都是做爲window對象的屬性和方法建立的
  • 某個執行黃精中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬(全局執行環境直到應用程序退出——例如關閉網頁或瀏覽器——時纔會被銷燬)
  • 每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中,函數執行完畢後,棧將其環境彈出,把控制權返回給以前的執行環境
  • 當代嗎在一個環境中執行時,會創造變量對象的一個做用域鏈(scope chain)。做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數有序的訪問
  • 做用域鏈最前端始終是當前執行的代碼所在環境的變量對象,若是這個環境是函數,則將其活動對象(activation object)做爲變量對象,活動對象在最開始時只包含一個變量,即arguments對象(這個對象在全局環境中是不存在的)。做用域鏈的最後一個對象始終都是全局執行環境的變量對象
  • 標識符解析是沿着做用域鏈一級一級的搜索標識符的過程,從最前端開始,而後逐級向後回溯,直到找到標識符爲止(找不到一般會發生錯誤)
// 函數changeColor()的做用域鏈包含兩個對象:
// 它本身的變量對象(其中定義着arguments對象)和全局環境的變量對象
var color = "blue";

function changeColor() {
    if (color === "blue") {
        color = "red";
    } else {
        color = "blue";
    }
}

changeColor();

alert("color is now " + color);        // "red"

延長做用域鏈

  • 有些語句尅在做用域鏈的前端臨時增長一個變量對象,該變量對象會在代碼執行後被移除。具體說就是執行流進入下列任何一個語句時,做用域鏈就會獲得加長前端

    • try-catch 語句的catch塊。catch語句會建立一個新的變量對象,其中包含的是被拋出錯誤對象的聲明
    • with語句,會將制定的對象添加到做用域鏈中

沒有塊級做用域

  • 在其餘類C語言中,由花括號封閉的代碼都有本身的做用域,但JavaScript不會
if (true) {
    var color = "blue";
}
alert(color);        // "blue"
  • 聲明變量,使用var聲明的變量會自動被添加到最接近的環境中,在函數內部,就是函數的局部環境
  • 查詢標識符,逐級向上查詢,若是存在一個局部的變量定義,則搜索中止。若是局部環境中存在着同名的標識符,就不會使用位於父環境中的標識符
var color = "blue";
function getColor() {
    var color = "red";
    return color
}
alert(getColor());        // "red"
alert(color);             // "blue"
  • 訪問局部變量要比訪問全局變量更快,由於不用向上搜索做用域鏈。不過JavaScript引擎在優化標識符查詢方面作的不錯,所以這個差異在未來恐怕就能夠忽略不計了

垃圾收集

  • JavaScript具備自動垃圾收集機制,即,執行環境會負責管理代碼執行過程當中使用的內存
  • 這種機制很簡單,找出那些再也不繼續使用的變量,而後釋放其佔用的內存,爲此垃圾收集器會按照固定的時間間隔(或代碼執行中預約的收集時間),週期性的執行這一操做

標記清除(mark-and-sweep)

  • 標記清除是JavaScript中最經常使用的垃圾收集方式
  • 當變量進入環境,就標記「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存。而當變量離開環境時,則將其標記爲離開環境

引用計數 (reference counting)

  • 當聲明瞭一個變量並將一個引用類型值賦給變量時,則這個值的引用次數就+1
  • 當引用了這個值的變量又被賦給了其餘值,則這個值的引用次數-1
  • 當這個值的次數變成0時,就能夠將其佔用的內存空間回收
  • 當兩個對象的屬性互相引用對方時,陷入死循環,將沒法回收,爲此Netscape在navigator 4.0中放棄了該方案

管理內存

  • 一旦數據再也不有用,最好經過將其值設置爲null來釋放引用——解除引用(dereferencing),這一作法適用於大多數全局變量和全局對象的屬性,而局部變量會自動被解除引用
  • 解除引用不會當即觸發回收機制,而是讓其脫離執行環境,以便下一次回收時釋放內存
相關文章
相關標籤/搜索