剖析JS 「萬物皆物件」的迷思
初學 JavaScript 時,偶爾會在教學文或討論區中看到這樣的說法:
「在 JavaScript 中,萬物皆為物件。」
便在潛意識中埋下一個 JS 中所有變數都是物件的種子。如今因為工作上大量使用 JS 這個語言,對 JS 這個語言有比較深一點的了解後,便想要回來探討這個議題。
先講結論,這個說法是不正確的。
但是,我相信正在看這篇文章的你應該也跟我一樣,會想要了解為什麼訪間會有 「JavaScript 萬物皆為物件」 的說法,以及這個說法背後的論點是什麼?反對這個說法的論點是什麼?本篇文章將帶大家探討這個議題,挖掘正反兩方的論點,並釐清一些 JS 的觀念。
為何會有「萬物皆物件」的說法?
JavaScript 的基本資料型別
在探討 「萬物皆為物件」 這個議題之前,我們首先需要了解 JavaScript 中的基本資料型別。JavaScript 中有七種資料型別,Undefined、Null、Boolean、Number、String、Symbol(ES6新增)、Object。其中,前六種是基本資料型別(Primitive Types),而 Object 是複雜資料型別。
由此可見,在 JavaScript 的世界中,並非所有的資料型別本質上都是物件。基本資料型別是獨立的,它們並不具備物件的屬性和方法。然而,在某些情況下,基本資料型別也可以表現出類似物件的行為,這正是 「萬物皆物件」 這種說法產生的原因之一。
「萬物皆物件」說法的起源
雖然基本資料型別和物件有著明顯的區別,但在實際使用中,我們常常發現基本資料型別可以像物件一樣操作。例如:
const a = "123";
console.log(a.__proto__)
/* String {
anchor: ƒ anchor()
at: ƒ at()
big: ƒ big()
blink: ƒ blink()
bold: ƒ bold()
charAt: ƒ charAt()
charCodeAt: ƒ charCodeAt()
codePointAt: ƒ codePointAt()
concat: ƒ concat()
constructor: ƒ String()
...
} */
const b = "456";
console.log(b.substr(1)); // "56"
從上述例子中可以看到,變數 a 和 b 雖然是基本資料型別 String,但它們都有 __proto__ 屬性,並且可以調用多種方法。這似乎印證了 「基本型別也是一種物件」 的說法。
「萬物皆物件」說法的誤區
包裝物件(Wrapper Object)
然而,這種理解其實是不正確的。事實上,這涉及到 JavaScript 中的包裝物件(Wrapper Object)機制。當我們對基本資料型別的變數調用方法時,JavaScript 會臨時創建一個對應的包裝物件,或稱作基本型別包裹器 (Primitive Wrapper),使其具有物件的行為。 例如:
const str = "hello";
console.log(str.toUpperCase()); // "HELLO"
在這裡,str 是一個字串,但調用 toUpperCase 方法時,JavaScript 會臨時創建一個 String 包裝物件,使其具有物件的行為。內部運行過程如下:
- 建立一個 String 的實例。
- 在實例上呼叫指定的方法。
- 銷毀這個實例。
具體過程如下所示:
const tempStr = new String("hello");
const result = tempStr.toUpperCase();
tempStr = null;
console.log(result); // "HELLO"