【JS迷你書】類型轉換之拆箱操做

衆所周知,JS 中共有 7 種數據類型:UndefinedNullBooleanNumberStringSymbolObject。前 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

  1. Let lref be the result of evaluating AdditiveExpression.
  2. Let lval be GetValue(lref).
  3. Let rref be the result of evaluating MultiplicativeExpression.
  4. Let rval be GetValue(rref).
  5. Let lnum be ToNumber(lval).
  6. Let rnum be ToNumber(rval).
  7. 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:

  1. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".
  2. If IsCallable(toString) is true then,
    1. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
    2. If str is a primitive value, return str.
  3. Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".
  4. If IsCallable(valueOf) is true then,
    1. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
    2. If val is a primitive value, return val.
  5. Throw a TypeError exception.

When the [[DefaultValue]] internal method of O is called with hint Number, the following steps are taken:

  1. Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".
  2. If IsCallable(valueOf) is true then,
    1. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
    2. If val is a primitive value, return val.
  3. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".
  4. If IsCallable(toString) is true then,
    1. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
    2. If str is a primitive value, return str.
      Throw a TypeError exception.
  5. 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 值採起不一樣的處理方式,好比 hintString 時,優先調用對象的 toString 方法,若是返回值是基本類型值,返回該值,不然調用對象的 valueOf 方法,若是返回值是基本類型值,返回該值。不然報錯。

hintNumber 時,順序是反過來的,優先調用 valueOf,若是其返回值不是基本類型,再調用 toString。另外,除了日期對象外,若是沒傳 hint 的話,其默認值是 Number,所以 JS 中類型轉化時,更偏心 Number

下面咱們舉幾個例子看看:

var a = {
  toString() {
    return 3
  },
  valueOf() {
    return '30'
  }
};
console.log(a - 5); // 25
複製代碼

這裏使用的是減法操做,此時 hintNumber,所以先調用對象 avalueOf 方法,其返回值 '30' 是字符串類型,是基本類型。所以 a - 5 變成了 '30' - 5

再看:

var a = {
  toString() {
    return {}
  },
  valueOfnull
};
console.log(a - 5); // Uncaught TypeError: Cannot convert object to primitive value
複製代碼

對象 a,其方法 valueOf 不是函數,於是看其 toString 方法,而該方法返回的是一個空對象,不是基本類型。於是報錯。

再如:

var o = {
  toString() {
    return 'now is: '
  },
  valueOffunction({
    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:

  1. Let lref be the result of evaluating AdditiveExpression.
  2. Let lval be GetValue(lref).
  3. Let rref be the result of evaluating MultiplicativeExpression.
  4. Let rval be GetValue(rref).
  5. Let lprim be ToPrimitive(lval).
  6. Let rprim be ToPrimitive(rval).
  7. 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)
  8. Return the result of applying the addition operation to ToNumber(lprim) and ToNumber(rprim). See the Note below 11.6.3.

其中第 五、6 步直接獲取加號兩邊的基本類型。此時沒有都傳遞 hint,o 是普通對象,所以默認 hintNumber,使用的 valueOf 的返回值。而 d 是日期對象,默認 hintString,優先調用的是其 toString 方法。而後根據第 7 步,採用的是字符串拼接方法。

加法操做,這裏再舉一例:

var o = {
   toStringfunction({
    return 2
  }
};
console.log(o + o); // 4
複製代碼

這裏不過多解釋了。

ToPrimitive 除了在四則運算中大量用到外,關係運算中也常常使用。好比 == 操做。其餘類型轉換等相關知識留給後續文章吧。

至此,「拆箱」已經說完了。

本文完。

《JavaScript 迷你書》傳送門,全面夯實基礎

掘金收藏

相關文章
相關標籤/搜索