我們從一道筆試題說起:
var str = 'string';
str.pro = 'hello';
console.log(str.pro + 'world');
輸出啥?要理解這個問題,我們得從頭說起。
Javascript 數據類型分兩大類,基本類型(或者說是原始類型)和引用類型。基本類型的值是保存在棧內存中的簡單數據段,共有五種,按值訪問,分別是 undefined null boolean number 和 string;而引用類型的值則是保存在堆內存中的對象,按引用訪問,主要有 Object Array Function RegExp Date等。
// 基本類型
var a = 10;
var b = true;
var c = 'string';
// 引用類型
var d = {};
var f = [];
var e = new String('abc');
我們再來回頭看這道筆試題,很顯然變量 str 是一個基本類型,str.pro 看上去是給 str 添加了一個屬性,等等,我們似乎只有在當 str 是一個對象時才看到過這樣的用法,似乎也已經習慣給對象添加 key-value 鍵值對,但是基本類型也行么?
這個問題先放一邊,我們回到標題中的問題:
var str = 'string';
console.log(str.length); // 6
str 變量並沒有 length 屬性,不是說好了只有對象才能用 . 或者 [] 去訪問屬性值嗎? 這里我們要引入一個叫做 基本包裝類型 的概念。除了 Object Array 等引用類型外,其實還有三種特殊的引用類型 String Number 和 Boolean,方便我們操作與其對應的基本類型,而它們就是基本包裝類型。str 作為一個基本類型是沒有 length 屬性的,但是它的基本包裝類型 String 有啊,其實在執行 console.log(str.length) 這段代碼時,事情的經過是這樣的:
- 創建String類型的一個實例
- 在實例上調用指定的方法
- 銷毀這個實例
所以獲取字符串變量 str 的長度的代碼,內部實現大概是這個樣子的:
var str = 'string';
var len = str.length;
console.log(len); // 6
var str = 'string';
var _str = new String(str);
var len = _str.length;
_str = null;
console.log(len); // 6
那么我們再回到文章開頭的例子,也就不難理解了。當執行 str.pro = 'hello' 時,實際上內部創建了一個基本包裝類型的實例,然后給這個實例的 pro 屬性賦值為 hello,實例創建后馬上銷毀了,當下一次試圖獲取 str.pro 的值時,又會創建一個基本包裝類型的實例,顯然新創建的實例時沒有 pro 屬性的,為 undefined,所以最后輸出 undefinedworld 。而下面的代碼也是一樣的道理:
var str = 1;
str.pro = 2;
console.log(str.pro + 10); // NaN
有了這個包裝器對象的概念,操作數字字符串就方便多了!
最后引用一段《Javascript啟示錄》中的話:
在針對字符串、數字和布爾值使用字面量時,只有在該值被視為對象的情況下才會創建實際的復雜對象。換句話說,在嘗試使用與構造函數關聯的方法或檢索屬性(如var len = 'abc'.length) 之前,一直在使用原始數據類型。當這種情況發生時,Javascript 會在幕后為字面量值創建一個包裝器對象,以便將該值視為一個對象。調用方法以后,Javascript 即拋棄包裝器對象,該值返回字面量類型。這就是字符串、數字、布爾值被認為是原始數據類型的原因。