“在JavaScript中的一切都是物件”這個說法一直讓我困惑。他們指的是什麼?一個函式或者陣列,它們怎麼同時也是一個物件?在我們解答這個問題前,我們需要知道JavaScript是如何對不同資料型別歸類的。
在JavaScript中,有兩個資料型別:基本型別和物件型別(物件型別有時候也被稱為引用型別)
基本型別 |
物件型別(引用型別) |
Number |
Function |
String |
Object |
Boolean |
Array |
Symbol |
|
null |
|
undefined |
根據這個分類,這個問題的簡單答案是:JavaScript中,並非一切值都是物件。是隻有屬於物件型別的值才是物件。也可以認為,任何非基本型別的都是物件型別。但是基本型別和物件型別有什麼區別呢?更重要的是,人們所說的“所有”或“幾乎所有”的JavaScript類似都是物件的真正含義是什麼?這裡說說主要的兩個區別:可變性和比較。
Mutability可變性
根據我的經驗,人們說的值是“類似物件”的真實含義是因為他們的可變性,更具體的說,是支援新增和刪除屬性。例如,因為函式和陣列屬於物件型別,你可以像物件一樣給它們新增屬性。
1 2 3 4 5 6 7 |
var func = function() {}; func.firstName = "Andrew"; func.firstName; // "Andrew" var arry = []; arry.age = 26; arry.age; // 26 |
可變性開啟了各種精彩的使用方式,也是理解原型和建構函式是如何工作的關鍵。
但是基本型別是不可變的,我們無法給它們新增屬性。如下面程式碼所示,即使我們給基本型別新增了屬性,解析器會無法在下一步讀取它們的值。
1 2 3 4 5 6 7 |
var me = "Andrew"; me.lastname = "Robbins"; me.lastname; // undefined var num = 10; num.prop = 11; num.prop; // undefined |
在這一點上,基本型別的值無法改變的真正含義,需要在更基本的層面檢查問題,如下程式碼所示:
1 |
1 = 2; // ReferenceError |
這似乎是一個愚蠢的例子,但是我認為能反映出我們現在討論的可變性,當你在JavaScript控制器中輸入數字 1,編譯器會給其分配基本型別,所以當你嘗試將數字1改變成數字2會失敗。
比較和傳遞
除了可變性,另外一個基本型別和物件型別重要的區別是他們在程式中比較和傳遞的方式。基本型別通過值來比較,而物件型別通過引用來比較,這是什麼意思呢?我們先看看基本型別,如下程式碼所示:
1 |
"a" === "a"; // true |
因為值“a”等於“a”所以為true,當我們在圖中引入變數會發生什麼呢?除了將一個基本型別儲存在變數中什麼也沒發生。
1 2 3 4 |
var a = "a", b = "a"; a === b; // true |
當基本型別通過值來比較,結果為true,變數a的值正好等於變數b的值,換句話說,”a”等於”a”。但是看看下面這個例子,如果我們在物件型別中應用相同的例子,我們會得到相反的結果。
1 2 3 4 |
var a = {name: "andrew"}, b = {name: "andrew"}; a === b; // false |
為什麼會這樣呢?如果想要兩者比較為真需要物件型別要引用同樣的型別。通過以上的例子,我們給變數b建立一個新的物件。就像David Flanagan說過:
我們說的通過引用進行物件比較是:兩個物件的值是否相同取決於它們是否指向相同的底層物件。
那我們這樣傳值會發生什麼?
1 2 3 4 5 6 |
var a = {name: "andrew"}, b = a; b.name = "robbins"; a === b; // true |
這個可能開始看上去很奇怪,但是仔細看看發生了什麼,因為物件是物件型別的一部分,它比較的值是按引用進行傳遞。引用的是相同的底層物件。在以上的例子中,我們設定b等於a。並沒有建立新物件,我們只是簡單地建立了一個對其他物件的引用。從另一個方面來看我們是將變數b指向a,所以當我們改變b的name屬性,我們同樣改變了a的name屬性。
如果將相同的例子應用在基本型別上呢?
1 2 3 4 5 6 |
var a = "Andrew", b = a; b = "Robbins"; a === b; // false |
當我們設定b等於a,請記住基本型別通過值來傳遞和比較,我們實際上另外建立了一個a的拷貝,所以我們改變b的值再跟a比較,兩個值是不一樣的。
Wrapper Objects包裝物件
有些人會說:“好,如果基本型別不是物件,為什麼我們可以呼叫他們的方法呢” 回答是包裝物件。
當你嘗試呼叫基本型別的方法,JavaScript在幕後做了一個巧妙的處理,將你的基本型別的值轉換成臨時物件用於建構函式,決定使用哪個建構函式取決於你嘗試改變的基本型別的值,在String中呼叫.length會使用string()建構函式臨時將基本型別轉變成物件—允許你使用length方法而改變它,這個臨時物件被稱為包裝物件。
有趣的是,null和undefined這兩個基本型別不能呼叫這樣的方法,否則會提示型別錯誤。
我們可以使用typeof來區分:
1 2 |
typeof "s"; // "string" typeof new String(s); // "object" |
備註:在執行typeof null 時js編譯器會返回object,是顯而易見的bug。
1 |
typeof null // "object" |
當然,考慮到JavaScript是用10天寫出來(http://www.quora.com/In-which-10-days-of-May-did-Brendan-Eich-write-JavaScript-Mocha-in-1995),就不過多去擔憂了 :)。
此外,我們也需要了解基本型別的屬性是隻讀和臨時的。
1 2 3 |
var hello = "hello"; hello.slice(1); // "ello" (Here we're actually calling slice not on hello, but of a copy of hello) hello; // "hello" |
Summary總結
JavaScript的值可以分為兩種型別:基本型別和物件型別,基本型別有:String, Number, Boolean, Symbol, undefined 和 null.,物件型別有Function, Object 和 Array.
基本型別和物件型別的區別在於可變性和比較的方式以及程式中傳值。
基本型別是不可變的,換種說法就是它們的值不能改變。對比而言,物件型別是可變的,它們的值可以更新和改變。
基本型別可以按值比較,當我們把一個基本型別賦值給另外一個基本型別,是複製了一個值。而物件這是通過引用進行比較,引用的是什麼呢?引用的是底層物件。當我們賦值一個物件給另一個物件時。引用指標就建立了。在這個情況下,改變一個物件的值將更新另外一個物件的值。
當我們嘗試在基本型別的值中呼叫方法時,JavaScript使用包裝物件來臨時控制基本型別,導致物件變為只讀的並在垃圾回收後執行。