本系列文章已重新編修,並在加入部分 ES6 新篇章後集結成書,有興趣的朋友可至天瓏書局選購,感謝大家支持。
購書連結 https://www.tenlong.com.tw/products/9789864344130
讓我們再次重新認識 JavaScript!
在這系列文中一直以來,我們強調在 JavaScript 這門程式語言當中,內建的型別主要可以分成基本型別 (Primitives) 與物件型別 (Object) 兩大類。
而在物件型別當中,又可以再細分出幾種「建構器」(Constructor):
String()
Number()
Boolean()
Array()
Object()
Function()
RegExp()
Date()
Error()
Symbol()
這些建構器都可以透過 new
關鍵字來產生一個對應物件,對 JavaScript 不熟悉的朋友甚至會把他們當做 Class 類別來看。 但事實上,這些 Constructor 只是 JavaScript 所提供的「內建函式」。
(還不知道為什麼 function 可以作為建構式的朋友可以參閱前一篇: 重新認識 JavaScript: Day 22 深入理解 JavaScript 物件屬性 )
那麼可能也有人發現了,在這些內建複合式物件的建構器當中,有幾個我們曾經在基本型別 (Primitives) 就已經見過的老朋友。
String()
Number()
Boolean()
別誤會了,上面這三種看似基本型別的東西,就算透過 new
來建立的值依然是「物件」,透過範例我們簡單做個比較:
var str = 'Hello';
typeof str; // "string"
var strObj = new String('Hello');
typeof strObj; // "object"
為什麼會有這種奇特的設定呢? 這個問題我認爲你應該更早之前就要提出來。
相信大家跟著這系列文看到這裡,都一定知道 string (字串) 是基本型別的一種。
var str = 'Hello';
console.log( str.length ); // 5
console.log( str.toUpperCase() ); // "HELLO"
那麼有沒有人想過這個問題,為什麼明明是「基本型別」卻會有「屬性」以及「方法」可以呼叫?
沒錯!你得到它了! 全都是「自動轉型」賦予 String、Number 與 Boolean 這些神奇的功能。
在 JavaScript 這門程式語言當中,當我們嘗試著要去存取 String、Number 與 Boolean 這三種基本型別的屬性時,它就只會在「那一刻」被轉型為該類別的「物件」。
var str = 'Hello';
console.log( str.length );
換句話說,像上面程式碼,當我們試著去讀取 str.length
的時候,背後原理是這樣的:
var str = new String("Hello");
str.length;
str = null;
str = 'Hello';
它會透過對應的物件建構器將 "Hello"
包裝成一個 String 的「物件」,然後回傳對應的屬性後,即刻銷毀恢復成基本型別。
var str = 'Hello';
typeof str; // "string"
var strObj = new String('Hello');
typeof strObj; // "object"
// 像這樣,分別為物件與基本型別的 string 設定「屬性」
strObj.color = 'red';
str.color = 'red';
console.log( strObj.color ); // 'red'
console.log( str.color ); // undefined
可以看到,分別為物件與基本型別的 string 設定自訂屬性 color
,在設定的時候並不會出錯,但事後要試著讀取時,「基本型別」的屬性仍然是 undefined
,「物件」的字串卻將 color
屬性給保留下來了。
而這樣的事情除了 String,在同樣有對應的物件型別的 Number 與 Boolean 也會發生。
而它們的「屬性」及「方法」當然就是由對應的物件所提供,這個對應的物件也就是我們所說的「基本型別包裹器」(Primitive Wrapper)。
為什麼叫「包裹器」呢,這些物件都有同樣特性:
var nameStr = new String("Kuro");
typeof nameStr; // "object"
nameStr instanceof String; // true
nameStr.valueOf(); // "Kuro"
var num = new Number(100);
typeof num; // "object"
num instanceof Number; // true
num.valueOf(); // 100
這些複合式物件我們可以透過 instanceof
來確認它的基本型別是什麼。
另外,當我們要取得原始的值時,可以透過 .valueOf()
來取得。 [註1]
而 null
與 undefined
在原本的設計上就是「空值」與「未定義」,所以自然沒有對應的物件型別,也不會有屬性與方法。
基本型別與包裹器自動轉型的關係,大概就像是遊戲王裡的武藤遊戲每次打不贏就會變身成前世法老王一樣,把把自摸,打完牌之後又馬上變回來 (咦
但這並不代表我們一開始就 變成法老王 透過 new
建構式生成物件是比較好的做法。
當我們要做型別判斷時,如同先前所說,透過 new
建構式生成的結果必定是物件,不管 String、Number 還是 Boolean 都是:
var str = 'Hello';
typeof str; // "string"
var strObj = new String('Hello');
typeof strObj; // "object"
另一個原因是基本型別在處理與運算時的效率也遠高於物件型別。
為什麼基本型別包裝成物件之後,就可以有各式各樣的方法來操作呢? 其實都是透過物件「原型」提供的方法繼承而來。 在理解了建構式、型別與包裹器之後,在後續的文章當中,我們要來談談「原型鍊」與「繼承」,敬請期待。
.valueOf()
我們曾在 重新認識 JavaScript: Day 06 運算式與運算子 與 重新認識 JavaScript: Day 07 「比較」與自動轉型的規則 介紹過,有興趣的朋友可以回頭參考。