JS 基礎型別之裝箱操作

老姚發表於2019-04-20

眾所周知,JS 是一門弱型別語言,不需要事先具體宣告變數的型別,因為會在程式執行過程中,型別會被自動推斷確定。因此,可以用同一個變數儲存不同型別的資料:

var a = 1;
a = 'abc';
a = {
  x: 1
};
複製程式碼

JS中目前共有7種資料型別:UndefinedNullBooleanNumberStringSymbolObject

前 6 者是基本型別資料,Object 是引用型別資料。注意,陣列和函式都是特殊的 Object

二者有什麼區別呢?

變數儲存基本型別資料時,是直接棧內儲存的,而引用型別是在堆中生成,在棧內儲存的是該物件的引用。

JS 基礎型別之裝箱操作

除此之外,還有一點不同就是:基本型別資料本身不可變,但引用型別資料是可變的。

引用型別資料是可變的,不言而喻,是指我們可以修改其屬性值。注意:物件本身就是資料容器。

var a = {
  x: 1
};
a.x = 2
console.log(a); // => {x: 2}
console.log(a.x); // 2
複製程式碼

a.x 或者 a['x'] 中“.”和“[]”操作符是專門獲取引用型別屬性的值操作。然而在 JS 中基本型別變數也是可以使用“點”的,這給初學者造成一定困惑,比如:

var a = 1;
a.x = 2;
console.log(a);// 1
console.log(a.x);// undefined
複製程式碼

其實,上述程式碼執行過程中發生了所謂的“裝箱”操作。

比如第二行:

a.x = 2
複製程式碼

等價於:

var temp = new Number(a)
temp.x = 2
temp = null
複製程式碼

因為 2 是基本型別,在取其屬性時,先用對應 Number 建構函式包裹成相應的臨時物件。然後再對此臨時物件進行操作,隨後臨時物件便銷燬。這個過程即“裝箱”。

知道這個過程後,那麼整段程式碼就好理解了:

var a = 1;
var temp1 = new Number(a);
temp1.x = 2;
temp1 = null;
console.log(a);// 1
var temp2 = new Number(a);
console.log(temp2.x);// undefined
temp2 = null;
複製程式碼

看到了嗎,兩次 a.x,有兩次“裝箱”是不同的臨時物件。因此結果也如預期一樣。

“裝箱”這種說法是來自其他語言的。其實叫啥名字無關緊要,主要是理解這個過程,不再迷糊。

接下來我們去看看 JS 文件規範裡是真怎麼描述的。

為了方便,我們看第 3 版就行,即《ECMA-262 3rd edition》。該規範比較早,當然也可以看最新的。

其中第 44 頁,第 11.2.1 小節,對屬性訪問器(Property Accessors)描述如下:

JS 基礎型別之裝箱操作

文字版:

The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:

  1. Evaluate MemberExpression.
  2. Call GetValue(Result(1)).
  3. Evaluate Expression.
  4. Call GetValue(Result(3)).
  5. Call ToObject(Result(2)).
  6. Call ToString(Result(4)).
  7. Return a value of type Reference whose base object is Result(5) and whose property name is Result(6). 它是以“[]”屬性訪問方式為例來說明的。

這裡再具體舉一個例子,來看看整個流程。比如,a = 1,然後獲取 a['x'] 。

1.計算表示式 a。(這裡用表示式是考慮到像這種情形:a.b['x'],此時表示式是 a.b)
2.獲取上一步結果的值,這裡是1。
3.計算表示式 'x'。(這裡用表示式是考慮到像這種情形:a['x'+'y'],此時表示式 'x'+'y')
4.獲取第 3 步的結果,即 'x'。
5.把第 2 步的結果傳入 ToObject,即 ToObject(1)。這裡記做 temp(可以看出這一步是關鍵。)
6.把第 4 步,轉化為字串,當然還是 'x'
7.返回,temp['x']。

其中第 5 步,最為關鍵。它用 ToObject 生成個臨時物件(因為它只是區域性變數),我們最後取到的屬性值正是這個物件的屬性值。

ToObject(第 48 頁)操作具體為:

JS 基礎型別之裝箱操作

與我們預知的一樣,布林、數值和字串這三種型別都生成相應的物件示例。而物件型別,直接返回本身。另外,UndefinedNull 型別是報錯的。

至此,JS 的“裝箱”操作說完了。

本文完。

相關文章