標籤:引用 文法 參考型別 ddt 存在 元素 success 添加 logs
ECMAScript 變數可能包含兩種不同資料類型的值:基本類型值和參考型別值。基本類型值指的是簡單的資料區段,而參考型別值指那些可能由多個值構成的對象。
在將一個值賦給變數時,解析器必須確定這個值是基本類型值還是參考型別值。第3 章討論了5 種基礎資料型別 (Elementary Data Type):Undefined、Null、Boolean、Number 和String。這5 種基礎資料型別 (Elementary Data Type)是按值訪問的,因為可以操作儲存在變數中的實際的值。
參考型別的值是儲存在記憶體中的對象。與其他語言不同,JavaScript 不允許直接存取記憶體中的位置,也就是說不能直接操作對象的記憶體空間。在操作對象時,實際上是在操作對象的引用而不是實際的對象。為此,參考型別的值是按引用訪問的①。
在很多語言中,字串以對象的形式來表示,因此被認為是參考型別的。ECMAScript 放棄了這一傳統。
4.1.1 動態屬性
定義基本類型值和參考型別值的方式是類似的:建立一個變數並為該變數賦值。但是,當這個值儲存到變數中以後,對不同類型值可以執行的操作則大相徑庭。對於參考型別的值,我們可以為其添加屬性和方法,也可以改變和刪除其屬性和方法。請看下面的例子:
① 這種說法不嚴密,當複製儲存著對象的某個變數時,操作的是對象的引用。但在為對象添加屬性時,操作的是實際的對象。
var person = new Object();person.name = "Nicholas";alert(person.name); //"Nicholas"
運行一下
以上代碼建立了一個對象並將其儲存在了變數person 中。然後,我們為該對象添加了一個名為name 的屬性,並將字串值"Nicholas"賦給了這個屬性。緊接著,又通過alert()函數訪問了這個新屬性。如果對象不被銷毀或者這個屬性不被刪除,則這個屬性將一直存在。
但是,我們不能給基本類型的值添加屬性,儘管這樣做不會導致任何錯誤。比如:
var name = "Nicholas";name.age = 27;alert(name.age); //undefined
運行一下
在這個例子中,我們為字串name 定義了一個名為age 的屬性,並為該屬性賦值27。但在下一行訪問這個屬性時,發現該屬性不見了。這說明只能給參考型別值動態地添加屬性,以便將來使用。
4.1.2 複製變數值
除了儲存的方式不同之外,在從一個變數向另一個變數複製基本類型值和參考型別值時,也存在不同。如果從一個變數向另一個變數複製基本類型的值,會在變數對象上建立一個新值,然後把該值複製到為新變數分配的位置上。來看一個例子:
var num1 = 5;var num2 = num1;
在此,num1 中儲存的值是5。當使用num1 的值來初始化num2 時,num2 中也儲存了值5。但num2中的5 與num1 中的5 是完全獨立的,該值只是num1 中5 的一個副本。此後,這兩個變數可以參與任何操作而不會相互影響。圖4-1 形象地展示了複製基本類型值的過程。
當從一個變數向另一個變數複製參考型別的值時,同樣也會將儲存在變數對象中的值複製一份放到為新變數分配的空間中。不同的是,這個值的副本實際上是一個指標,而這個指標指向儲存在堆中的一個對象。複製操作結束後,兩個變數實際上將引用同一個對象。因此,改變其中一個變數,就會影響另一個變數,如下面的例子所示:
var obj1 = new Object();var obj2 = obj1;obj1.name = "Nicholas";alert(obj2.name); //"Nicholas"
首先,變數obj1 儲存了一個對象的新執行個體。然後,這個值被複製到了obj2 中;換句話說,obj1和obj2 都指向同一個對象。這樣,當為obj1 添加name 屬性後,可以通過obj2 來訪問這個屬性,因為這兩個變數引用的都是同一個對象。圖4-2 展示了儲存在變數對象中的變數和儲存在堆中的對象之間的這種關係。
4.1.3 傳遞參數
ECMAScript 中所有函數的參數都是按值傳遞的。也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變數複製到另一個變數一樣。基本類型值的傳遞如同基本類型變數的複製一樣,而參考型別值的傳遞,則如同參考型別變數的複製一樣。有不少開發人員在這一點上可能會感到困惑,因為訪問變數有按值和按引用兩種方式,而參數只能按值傳遞。
在向參數傳遞基本類型的值時,被傳遞的值會被複製給一個局部變數(即具名引數,或者用ECMAScript 的概念來說,就是arguments 對象中的一個元素)。在向參數傳遞參考型別的值時,會把這個值在記憶體中的地址複製給一個局部變數,因此這個局部變數的變化會反映在函數的外部。請看下面這個例子:
function addTen(num) { num += 10; return num;}var count = 20;var result = addTen(count);alert(count); //20,沒有變化alert(result); //30
運行一下
這裡的函數addTen()有一個參數num,而參數實際上是函數的局部變數。在調用這個函數時,變數count 作為參數被傳遞給函數,這個變數的值是20。於是,數值20 被複製給參數num 以便在addTen()中使用。在函數內部,參數num 的值被加上了10,但這一變化不會影響函數外部的count 變數。參數num 與變數count 互不相識,它們僅僅是具有相同的值。假如num 是按引用傳遞的話,那麼變數count的值也將變成30,從而反映函數內部的修改。當然,使用數值等基本類型值來說明按值傳遞參數比較簡單,但如果使用對象,那問題就不怎麼好理解了。再舉一個例子:
function setName(obj) { obj.name = "Nicholas";}var person = new Object();setName(person);alert(person.name); //"Nicholas"
運行一下
以上代碼中建立一個對象,並將其儲存在了變數person 中。然後,這個變數被傳遞到setName()函數中之後就被複製給了obj。在這個函數內部,obj 和person 引用的是同一個對象。換句話說,即使這個變數是按值傳遞的,obj 也會按引用來訪問同一個對象。於是,當在函數內部為obj 添加name屬性後,函數外部的person 也將有所反映;因為person 指向的對象在堆記憶體中只有一個,而且是全域對象。有很多開發人員錯誤地認為:在局部範圍中修改的對象會在全域範圍中反映出來,就說明參數是按引用傳遞的。為了證明對象是按值傳遞的,我們再看一看下面這個經過修改的例子:
function setName(obj) { obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg";}var person = new Object();setName(person);alert(person.name); //"Nicholas"
這個例子與前一個例子的唯一區別,就是在setName()函數中添加了兩行代碼:一行代碼為obj重新定義了一個對象,另一行代碼為該對象定義了一個帶有不同值的name 屬性。在把person 傳遞給setName()後,其name 屬性被設定為"Nicholas"。然後,又將一個新對象賦給變數obj,同時將其name屬性設定為"Greg"。如果person 是按引用傳遞的,那麼person 就會自動被修改為指向其name 屬性值為"Greg"的新對象。但是,當接下來再訪問person.name 時,顯示的值仍然是"Nicholas"。這說明即使在函數內部修改了參數的值,但原始的引用仍然保持未變。實際上,當在函數內部重寫obj 時,這個變數引用的就是一個局部對象了。而這個局部對象會在函數執行完畢後立即被銷毀。
可以把ECMAScript 函數的參數想象成局部變數。
4.1.4 檢測類型
要檢測一個變數是不是基礎資料型別 (Elementary Data Type)?第3 章介紹的typeof 操作符是最佳的工具。說得更具體一點,typeof 操作符是確定一個變數是字串、數值、布爾值,還是undefined 的最佳工具。如果變數的值是一個對象或null,則typeof 操作符會像下面例子中所示的那樣返回"object":
var s = "Nicholas";var b = true;var i = 22;var u;var n = null;var o = new Object();alert(typeof s); //stringalert(typeof i); //numberalert(typeof b); //booleanalert(typeof u); //undefinedalert(typeof n); //objectalert(typeof o); //object
運行一下
雖然在檢測基礎資料型別 (Elementary Data Type)時typeof 是非常得力的助手,但在檢測參考型別的值時,這個操作符的用處不大。通常,我們並不是想知道某個值是對象,而是想知道它是什麼類型的對象。為此,ECMAScript提供了instanceof 操作符,其文法如下所示:
result = variable instanceof constructor
如果變數是給定參考型別(根據它的原型鏈來識別;第6 章將介紹原型鏈)的執行個體,那麼instanceof 操作符就會返回true。請看下面的例子:
alert(person instanceof Object); // 變數person 是Object 嗎?alert(colors instanceof Array); // 變數colors 是Array 嗎?alert(pattern instanceof RegExp); // 變數pattern 是RegExp 嗎?
根據規定,所有參考型別的值都是Object 的執行個體。因此,在檢測一個參考型別值和Object 建構函式時,instanceof 操作符始終會返回true。當然,如果使用instanceof 操作符檢測基本類型的值,則該操作符始終會返回false,因為基本類型不是對象。
使用typeof 操作符檢測函數時,該操作符會返回"function"。在Safari 5 及之前版本和Chrome 7 及之前版本中使用typeof 檢測Regex時,由於規範的原因,這個操作符也返回"function"。ECMA-262 規定任何在內部實現[[Call]]方法的對象都應該在應用typeof 操作符時返回"function"。由於上述瀏覽器中的Regex也實現了這個方法,因此對Regex應用typeof 會返回"function"。在IE 和Firefox 中,對Regex應用typeof 會返回"object"。
更多章節教程:http://www.shouce.ren/api/view/a/15218
4.1 基本類型和參考型別的值【JavaScript進階程式設計第三版】