再談:JavaScript 中的對象是如何進行類型轉換的?

最近我讀了 GitHub 上的一個倉庫項目 《What the f*ck JavaScript?》,列舉了 JavaScript 語言上比較怪癖的一些特性。東西比較多,看起來也有點雜。javascript

其中有至關一部份內容涉及到類型轉換——java

  • 若是根據轉換形式分的話:分隱式和顯式的
// 1. 隱式轉換:使用 `==` 運算符比較
[] == 0    // -> true
// 2. 顯式轉換:好比下面顯式調用了 `toString` 方法,轉成字符串
[].toString() // -> ""
複製代碼
  • 根據數據類型分的話:分基本類型轉換和對象類型轉換。
// 1. 基本類型轉換
0 == +false;
// 2. 對象類型轉換
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
複製代碼

最開始咱們接觸這些東西的時候,有些摸不着頭腦,感受很煩的。git

其實裏面是有規則的,也很少。排除掉規則相對簡單的基本類型轉換,總結下來,實際困擾咱們的核心點有兩個:github

  1. 什麼時候發生隱式類型轉換?
  2. 還有,對象的類型轉換流程又是怎樣的?

什麼時候發生隱式類型轉換?算法

一般發生在比較運算(==!=><)和算術運算(+-*/%)中,並且運算符兩邊的操做數不是同一類型。數組

注: 相等運算符 == 有一條特別的規則,在規範中定義——就是 null == undefined 的比較結果返回 truebash

對象的類型轉換流程又是怎樣的?函數

當咱們談及對象的類型轉換時,一般講的是對象到基本類型的轉換。當上述描述的場景發生時,對象轉換規則就開始起做用了。post

規則梳理起來並不複雜,就那麼幾條。由於一年前,我針對對象到基本類型轉換作了簡單的介紹,所以這次文章命名使用了「再談」。ui

此次我會盡可能詳細的給你們介紹轉換內部的機制,帶領你們理解透對象究竟是怎樣轉換的。

對象屬性: [Symbol.toPrimitive]

對象發生類型轉換時,首先會檢查對象上是否存在 [Symbol.toPrimitive] 屬性,若是存在的話就調用。該屬性存在一些限制:它必須是一個函數,並且返回值必須是基本類型,不然就要報錯:

1). 不是函數

2). 函數返回值不是基本類型

hint 值

[Symbol.toPrimitive] 在調用時,系統會自動給與一個參數 hint,這個 hint 能夠理解爲,這次對象發生轉換的預期類型爲什麼。

hint 有三種可能取值:'string''number''default'

下面咱們來演示下,這三種狀況的發生場景。

var obj = {
    [Symbol.toPrimitive](hint) {
        console.log('[Symbol.toPrimitive]', hint)
    }
}
undefined
// 第一種狀況(指望類型是 number):
+obj
// [Symbol.toPrimitive] number
// NaN
// 第二種狀況(指望類型是 string):
String(obj)
// [Symbol.toPrimitive] string
// "undefined"
// 第三種狀況(未知):
obj + ':('
// [Symbol.toPrimitive] default
// "undefined:("
obj + 1
// [Symbol.toPrimitive] default
// NaN
複製代碼

[Symbol.toPrimitive] 方法裏咱們沒有定義返回值,所以方法返回值是默認的 undefined

  1. 第一種狀況:+obj -> +undefined -> Number(undefined) -> NaN
  2. 第二種狀況:String(obj) -> String(undefined) -> "undefined"
  3. 第三種狀況:
    • obj + ':(' -> undefined + ':(' -> "undefined:("
    • obj + 1 -> undefined + 1 -> NaN

然而當對象不存在 [Symbol.toPrimitive] 的時候,轉換規則又是怎樣的呢?

這就關係到 Object.prototype 對象上的兩個方法了:valueOftoString

Object.prototype.valueOf / Object.prototype.toString

當對象上不存在 [Symbol.toPrimitive] 屬性的時候,若發生類型轉換,就要用到 Object.prototype 對象上的 valueOftoString 兩個方法了。

兩個方法調用有前後,可能都會調用,也能夠只調用一個就完成轉換,返回結果。這跟方法返回值和 hint 值有關係。

具體是:

  1. 若是 hint 值爲 'default',則與 hint 爲 'number' 時同樣對待。
  2. 若是 hint 值爲 'number',則
    • 先調用對象的 valueOf 方法,若是方法返回的是一個基本類型值,則對象的轉換結果就是這個返回值;
    • 不然,接着調用對象的 toString 方法。
  3. 若是 hint 值爲 'string',則
    • 先調用對象的 toString 方法,若是方法返回的是一個基本類型值,則對象的轉換結果就是這個返回值;
    • 不然,接着調用對象的 valueOf 方法。

你可能要問了,若是兩個方法的返回值都是對象的話,豈不是得不到對象最終的轉換結果了?一點都沒錯,咱們來試一下:

上圖裏,咱們在對象 obj 上定義了 valueOftoString 方法,在發生類型轉換時,覆蓋掉原型對象上的同名方法,以便咱們能更加真切地感覺到對象內部的實際的轉換流程。

咱們製造了一個極端狀況,兩個方法都沒有返回基本類型只而是對象,結果呢?而後控制檯就報錯了,告訴咱們不要這樣玩。

須要說明的是——在 JavaScript 中,當咱們在一個對象上調用 valueOf 方法的時候,實際上調用的是 Object.prototype.valueOf 這個原型方法。默認這個方法的返回值始終是調用對象自身,也就是說 valueOf 方法的返回的始終是對象,而非一個基本類型值。

注:

  1. Date 對象除外,由於 Date.prototype 上定義的 valueOf 方法覆蓋掉了 Object.protortype 上的。在 Date 對象上調用 valueOf方法,返回的是時間對象內部的時間戳表示。

  2. 另外,Date.prototype 也定義了本身的 [Symbol.toPrimitive] 屬性,默認是不可寫入的

這就獲得了一個結論:對象發生到基本類型轉換時,最終轉換結果就是 obj.toString() 返回值!這就解釋了下面代碼裏的輸出結果:

var obj = {}
// (1)
obj + ' :)' // "[object Object] :)"
// (2)
obj - 1 // NaN
複製代碼
  1. obj + ' :)' -> '[object Object]' + :)' -> '[object Object] :)'
  2. obj - 1 -> '[object Object]' - 1 -> Number('[object Object]') - 1 -> NaN - 1 -> NaN

總結:對象轉換算法步驟

總結下來,一個對象轉換到基本類型的算法步驟以下:

  1. 首先,檢查對象上是否有 [Symbol.toPrimitive] 屬性:
    • 有的話,調用此方法(屬性),此方法的返回值即對象最終的轉換值,
    • 沒有的話,進入第二步。
  2. 檢查當前轉換的 hint 值:
    • 若是爲 'default' / 'number'
      • 先調用對象的 valueOf 方法,若是方法返回的是一個基本類型值,則對象的轉換結果就是這個返回值,
      • 不然,接着調用對象的 toString 方法。
    • 若是爲 'string'
      • 先調用對象的 toString 方法,若是方法返回的是一個基本類型值,則對象的轉換結果就是這個返回值,
      • 不然,接着調用對象的 valueOf 方法。

數組的轉換

數組本質也是對象,所以上面的轉換規則也適應於它。不一樣的是數組原型對象上定義了本身的實現方法:Array.prototype.toString,這個方法覆蓋掉了 Object.prototype 上的同名方法。

重寫以後的 toString 方法的邏輯(相似)以下:

Array.prototype.toString = function () { return this.join() }
複製代碼

由於並未覆蓋 valueOf 方法(也是返回對象自己,對應到這裏就是數組實例),所以數組轉換結果就是調用 array.join() 的返回結果。

下面舉幾個例子:

var emptyArray = []
var array1 = [123]
var array2 = ['hi', 'How are you']

// (1)
Number(emptyArray) // 0
// (2)
array1 - 100 // 23
// (3)
array2 + '?' // "hi,How are you?"
複製代碼

對應的轉換過程以下:

  1. emptyArray 首先會轉換成空字符 ''Numbe('') 的結果就是 0
  2. array1 會轉換成字符串 '123''123' - 100 至關於 Number('123') - 100,也就是 23
  3. array2 會轉換成字符串 'hi,How are you''hi,How are you' + '?' 的結果天然是 'hi,How are you?'

到這裏的話,對象類型轉換的內容就講完了,但願能幫助到你們!

最後

感謝你花費寶貴的時間閱讀這篇文章,但願你能有所收穫。若是你以爲這篇文章讓你的生活美好了一點點,歡迎給我點贊或者評論建議。謝謝!

(完)

相關文章
相關標籤/搜索