[JavaScript] 型別系統 — Primitive & Object/Reference Type
在 JavaScript 中,有原始型別和物件型別二種。
Primitive Type 原始型別(或稱基本型別)
細分成以下六種:
string、number、boolean、null、undefined、symbol(於 ECMAScript 6 新定義)
- string — 字串,為零或多個字元組成的有限序列
- number — 整數、浮點數…etc
- boolean — true 或 false
- null — 空值
- undefined — 已宣告的變數,但尚未賦值
- symbol — 唯一值
除了上述外,Everything else is an object 🙌🏻
其它皆為物件,歸類於 Object/Reference Type 物件型別(或稱參考型別)
首先,要知道幾件事,
- 原始型別只是一個值,無法設定屬性和方法,且不可變(immutable)
以下範例使用 String.prototype
下的內建方法,得到處理後新的回傳值,但實際上並不會修改到它原本的值:
var a = 'hello world';a.toUpperCase(); //'HELLO WORLD'a.slice(-5); //'world'console.log(a); //'hello world'
可能你又會想,
為什麼說原始型別無法設定屬性和方法,但像 string 內建不是有讀取字串長度的 length 屬性嗎?剛剛也有使用到 slice 方法擷取某一段字串阿@@
var a = 'hello';a.length //5
這是因為…
當我們試著讀取字串 a
的屬性時,JavaScript 會先透過 new String(a)
將字串值強制轉型(coercion)成物件。這邊的 new
關鍵字和 String()
建構子用來創建一個新物件,而 String()
同時也是一個 object wrapper,它繼承了所有字串的屬性和方法,在讀取屬性、呼叫方法完後,object wrapper 就會被丟棄。
也有另一種說法,把上述行為稱為 Boxing 📦(不過這個用詞沒有出現在官方文件中)。
概念就是,原始型別會被自動裝箱(boxing)成物件型別,用完再自動拆箱(unboxing),以節省記憶體空間。
其中,有三個原始型別 string、number、boolean 存在著它對應的物件型別(或可說成是 object wrapper),分別為 String、Number、Boolean。
以下用 typeof
運算子檢驗其型別:
<!-- 原始型別 -->typeof 'Hello'; //'string'typeof 10; //'number'typeof true; //'boolean'____________________________________________________________________var a;typeof a; //'undefind'var b = null;typeof b; //'object'(不要懷疑!🤨此為 JavaScript 永久錯誤,怕會影響現有程式而不再修正)____________________________________________________________________typeof String('Hello'); //'string'typeof Number(10); //'number'typeof Boolean(true); //'boolean'____________________________________________________________________<!-- 物件型別 -->typeof new String('Hello'); //'object'typeof new Number(10); //'object'typeof new Boolean(true); //'object'
而物件型別可以再用 Object.prototype.valueOf()
取得原始型別:
typeof (new String('Hello')).valueOf(); //'string'typeof (new Number(10)).valueOf(); //'number'typeof (new Boolean(true)).valueOf(); //'boolean'
- 原始型別皆屬於傳值(Passed by Value)
此例先把原始型別數字 10
賦值到變數 a
中,再把 a
的值賦予至新變數 b
,並改變 b
值為 1010
,最後印出 a
和 b
的結果,很直觀地會是 10
和 1010
。
原因是當 a
和 b
在被宣告時,會在記憶體中各自產生一個位置,彼此獨立、互不影響。
var a = 10;
var b = a;
b = 1010;console.log(a); //10
console.log(b); //1010
而原始型別在比較時,是以真實的值進行比較:
var x = 1;
var y = true;console.log(x == y); //true
console.log(x === y); //false
上述 == (標準相等)可參考 ECMAScript-262 5.1 版標準的第 7 點:
If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
如此可得知,== 會透過 ToNumber 內部運算自動進行隱性轉換,true 會被強行轉為 1,所以 1 == 1 的結果得到 true。
而 === (嚴格相等)可參考 ECMAScript-262 5.1 版標準的第 1 點:
If Type(x) is different from Type(y), return false.
=== 一開始就會進行型別的比較,number 和 boolean 型別不同,結果因此為 false。
Object/Reference Type 物件型別(或稱參考型別)
含以下幾種:
Array、Object、Function
而像 JavaScript 內建物件 Date、Math、RegExp、JSON、Window、Document… 都算在此類!
首先,先了解什麼是「物件」?
JavaScript 將物件定義為無序的屬性集合。 換句話說,物件是由「鍵/名稱」 (key/name) 與「值」 (value) 組成,其中值可以是原始值、物件或函數。
var dog = {
name: 'Eta',
type: 'Shiba Inu'
age: 5,
address: {
country: 'Canada',
city: 'Vancouver, BC'
},
bark: function() {
console.log(this.name + ' goes bow-wow!');
}
}
- 物件型別可以設定屬性和方法,為可變(mutable)
承上範例,使用 .
存取 dog
物件的屬性 age
並修改其值,方式如下:
dog.age = 7;console.log(dog);
//由印出結果可知,原物件 dog 的屬性 age 已被更改為 7
//{
// name: 'Eta',
// type: 'Shiba Inu',
// age: 7,
// address: {
// country: 'Canada',
// city: 'Vancouver, BC'
// },
// bark: function() {
// console.log(this.name + ' goes bow-wow!');
// }
//}
- 物件型別皆屬於傳址(Passed by Reference)
此例先把一個物件 {name: ‘Eta’}
賦值到變數 a
中,再將物件 a
賦予至新變數 b
,並改變物件 b
的 name
屬性值為 Momo
,最後印出原 a
的 name
屬性,會發現它的值也一起被修改了,而比較 a
與 b
,結果為相等。
原因是當a
賦予至新變數 b
時,其實不會再創建一個新的記憶體位置,反之,兩個變數的值都是儲存在相同位置、指向同一個物件。
var a = {name: 'Eta'};
var b = a;
b.name = 'Momo';console.log(a.name); //'Momo'
console.log(a === b); //true
📖 參考資料:
JavaScript — Reference vs Primitive Values/ Types