衆所周知,JS 中共有 7 種數據類型:Undefined
、Null
、Boolean
、Number
、String
、Symbol
和 Object
。前 6 者是基本類型,Object
是引用類型。javascript
《類型轉換之裝箱操做》一文中說,由於 JS 是弱類型語言,咱們能夠像對待引用類型同樣對基本類型數據進行引用類型「才該有的」屬性獲取操做。html
好比,以下的代碼並不會報錯:java
var a = 1;
a.x = 2;
複製代碼
上述代碼運行過程當中,發生了「裝箱操做」,經過閱讀《ECMA-262》規範,咱們知道瀏覽器內部是調用 ToObject
操做來實現的,它把基本類型包裝成相應的引用類型。例如把 1
包裝成了 new Number(1)
。git
本文的主題關注相反的操做:對引用類型進行那些基本類型「才該有的」操做時會怎樣?即,「拆箱操做」。github
好比,以下的代碼並不會報錯:算法
var a = 1;
var b = {};
console.log(a - b);
複製代碼
對普通對象進行減法操做時,對象須要轉化爲數字類型。《Ecma-262 Edition 5.1》第11.6.2節對減法操做符規範以下:瀏覽器
The production AdditiveExpression : AdditiveExpression - MultiplicativeExpression is evaluated as follows: app
- Let lref be the result of evaluating AdditiveExpression.
- Let lval be GetValue(lref).
- Let rref be the result of evaluating MultiplicativeExpression.
- Let rval be GetValue(rref).
- Let lnum be ToNumber(lval).
- Let rnum be ToNumber(rval).
- Return the result of applying the subtraction operation to lnum and rnum. See the note below 11.6.3.
上述操做中第 五、6 步比較關鍵,調用了內部操做 ToNumber
:less
Argument Type | Result |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | The result is 1 if the argument is true. The result is +0 if the argument is false. |
Number | The result equals the input argument (no conversion). |
String | See grammar and note below. |
Object | Apply the following steps: 1. Let primValue be ToPrimitive(input argument, hint Number). 2. Return ToNumber(primValue). |
最後一行,處理Object
時,經歷兩步:1. ToPrimitive
。2. ToNumber
。函數
而 ToPrimitive
操做正與 ToObject
相對,表示轉化爲基本類型:
Input Type | Result |
---|---|
Undefined | The result equals the input argument (no conversion). |
Null | The result equals the input argument (no conversion). |
Boolean | The result equals the input argument (no conversion). |
Number | The result equals the input argument (no conversion). |
String | The result equals the input argument (no conversion). |
Object | Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8. |
最後一行說,對象轉化爲基本類型時,是獲取的對象的默認值。使用的是內部[[DefaultValue]](hint)
,規範原文引用以下(補充:本文中的英文均可以不看的,我都會仔細說明的):
When the [[DefaultValue]] internal method of O is called with hint String, the following steps are taken:
- Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".
- If IsCallable(toString) is true then,
- Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
- If str is a primitive value, return str.
- Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".
- If IsCallable(valueOf) is true then,
- Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
- If val is a primitive value, return val.
- Throw a TypeError exception.
When the [[DefaultValue]] internal method of O is called with hint Number, the following steps are taken:
- Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".
- If IsCallable(valueOf) is true then,
- Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
- If val is a primitive value, return val.
- Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".
- If IsCallable(toString) is true then,
- Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
- If str is a primitive value, return str.
Throw a TypeError exception.- When the [[DefaultValue]] internal method of O is called with no hint, then it behaves as if the hint were Number, unless O is a Date object (see 15.9.6), in which case it behaves as if the hint were String.
When the [[DefaultValue]] internal method of O is called with no hint, then it behaves as if the hint were Number, unless O is a Date object (see 15.9.6), in which case it behaves as if the hint were String.
上述算法是說,根據 hint
值採起不一樣的處理方式,好比 hint
是 String
時,優先調用對象的 toString
方法,若是返回值是基本類型值,返回該值,不然調用對象的 valueOf
方法,若是返回值是基本類型值,返回該值。不然報錯。
而 hint
是 Number
時,順序是反過來的,優先調用 valueOf
,若是其返回值不是基本類型,再調用 toString
。另外,除了日期對象外,若是沒傳 hint
的話,其默認值是 Number
,所以 JS 中類型轉化時,更偏心 Number
。
下面咱們舉幾個例子看看:
var a = {
toString() {
return 3
},
valueOf() {
return '30'
}
};
console.log(a - 5); // 25
複製代碼
這裏使用的是減法操做,此時 hint
是 Number
,所以先調用對象 a
的 valueOf
方法,其返回值 '30'
是字符串類型,是基本類型。所以 a - 5
變成了 '30' - 5
。
再看:
var a = {
toString() {
return {}
},
valueOf: null
};
console.log(a - 5); // Uncaught TypeError: Cannot convert object to primitive value
複製代碼
對象 a
,其方法 valueOf
不是函數,於是看其 toString
方法,而該方法返回的是一個空對象,不是基本類型。於是報錯。
再如:
var o = {
toString() {
return 'now is: '
},
valueOf: function() {
return "時間是:"
}
};
var d = new Date();
console.log(o + d); // 時間是:Mon May 06 2019 13:56:39 GMT+0800 (中國標準時間)
複製代碼
這裏使用了加法操做:
The production AdditiveExpression : AdditiveExpression + MultiplicativeExpression is evaluated as follows:
- Let lref be the result of evaluating AdditiveExpression.
- Let lval be GetValue(lref).
- Let rref be the result of evaluating MultiplicativeExpression.
- Let rval be GetValue(rref).
- Let lprim be ToPrimitive(lval).
- Let rprim be ToPrimitive(rval).
- If Type(lprim) is String or Type(rprim) is String, then
Return the String that is the result of concatenating ToString(lprim) followed by ToString(rprim)- Return the result of applying the addition operation to ToNumber(lprim) and ToNumber(rprim). See the Note below 11.6.3.
其中第 五、6 步直接獲取加號兩邊的基本類型。此時沒有都傳遞 hint
,o 是普通對象,所以默認 hint
是 Number
,使用的 valueOf
的返回值。而 d 是日期對象,默認 hint
是 String
,優先調用的是其 toString
方法。而後根據第 7 步,採用的是字符串拼接方法。
加法操做,這裏再舉一例:
var o = {
toString: function() {
return 2
}
};
console.log(o + o); // 4
複製代碼
這裏不過多解釋了。
ToPrimitive
除了在四則運算中大量用到外,關係運算中也常常使用。好比 ==
操做。其餘類型轉換等相關知識留給後續文章吧。
至此,「拆箱」已經說完了。
本文完。