JavaScript 對象轉值的原理

JavaScript 做爲一門弱類型的語言,在類型轉換上很是的靈活。它在提供便利的同時,也帶來了很多困惑。javascript

[] + [] , [] + {} , ${obj} ,這些表達式計算結果是什麼,又是如何得出這個結果的?java

參與數學計算的運算數必須是值類型,而且類型相同。在強類型的語言中,類型不一樣的值參與計算會致使編譯錯誤。數組

對於 js 來講,它會在運行時「智能」的進行類型轉換,獲得一個使用者可能指望的結果。函數

但若是使用者對這個結果不滿意,但願自定義,就須要瞭解運行時類型轉換的原理。ui

類型轉換會發生在值類型與值類型,值類型與對象類型之間。這裏咱們主要來探討一下對象到值類型的轉換過程。this

求值的基礎

JavaScript 中存在的六種值類型:Null, Undefined, Boolean, Number, String, Symbol。spa

既然是對象,就說明它存在,不能轉爲 Null 和 Undefined 類型。code

同理,對象必定非空,轉 Boolean 類型爲 true對象

對象不會自動發生 Symbol 轉換,除非使用強制類型轉換函數 Symbol()ip

比較常見的狀況是,對象轉 Number 和 String。

根據 EMACScript 規範,對象轉值(toPrimitive)類型會產生三種的 hint,分別是 numberstringdefault

咱們能夠根據 hint 來自定義轉換的目標值。

number

顯式地調用 Number() 函數,或者對象參與數學計算時:

let a = Number(obj); // NaN
let b = +obj; // NaN
let c = obj1 - obj2; // NaN
複製代碼

雖然默認的計算結果爲 NaN ,但它屬於 number 轉換。

string

顯式地調用 String() 函數,或者某些指望接受 String 類型的參數,當傳入對象時,會發生對象到 string 的轉換。

String(obj); // '[object Object]'
console.log( `${obj}` ); // [object Object]
anotherObj[obj] = 123; // { '[object Object]': 123 }
複製代碼

字符串模板接受字符串參數,傳入非字符串類型,會發生隱式轉換。

對象的屬性名支持字符串和 Symbol 類型, 除這兩種類型外,會默認轉爲字符串。

default

當不能肯定某個操做指望獲得什麼類型時,例如二元運算符 + 的結果,多是字符串也多是數字。所以沒法肯定是把運算數轉爲 number 仍是 string

這會產生一個 default 類型的 hint。

對象與 String, Number, Symbol 類型進行判等,也沒法肯定轉換的目標類型。

// 產生 default 類型的 hint
obj1 + obj2;
obj == 1;
複製代碼

咱們沒必要記憶哪些狀況是 default 類型的轉換,後面會講到,它的轉換處理流程和 number 相同。

特殊的是,比較操做符,例如: >< ,雖然能用於比較 Number 和 String 類型,但它們的 hint 是 number ,這是歷史遺留問題。

類型轉換的過程

在實現上,對象到值類型的轉換,會分三個步驟:

一、判斷對象是否存在一個屬性名爲 Symbol.toPrimitive 的函數,若是存在調用它,並傳入以上三種之一的 hint 做爲參數。若是這個函數返回值類型,就是對象轉值的結果;若是返回非值類型,會拋出異常。若是不存在這個屬性,執行下一步。

二、hint 類型若是是 string ,會依次調用對象的 toString()valueOf() 函數,直到某一個函數在該對象實現,並返回值類型爲止。若是返回非值類型,會忽略這個方法。

三、hint 類型若是是 number 或者 default ,會依次調用對象的 valueOf()toString() ,直到某個函數在該對象實現,並返回值類型爲止。若是返回非值類型,會忽略這個方法。

Symbol.toPrimitive

一個實現 Symbol.toPrimitive 屬性的 user 對象:

const user = {
  name: 'Smallfly',
  age: '27',
  [Symbol.toPrimitive](hint) {
    console.log( `[${hint}]` );
    if (hint === 'string') {
      console.log('name: ' + this.name);
      return this.name;
    } else {
      console.log('age: ' + this.age);
      return this.age;
    }
  }
}

console.log(`${user}`); // [string] name: Smallfly -- Smallfly
console.log(+user); // [number] age: 27 -- 27
console.log(user + 1); // [default] age: 27 -- 27
複製代碼

user 傳入字符串模板觸發了 string 轉換; + 一元運算符觸發了 number 轉換; + 二元運算符觸發 default 轉換。

Symbol.toPrimitive 函數包含了 user 轉簡單值類型的全部狀況。

toString/ValueOf

若是對象沒有實現 Symbol.toPrimitive 方法,會繼續嘗試調用 toStringvalueOf 方法。

const user = {
  name: 'Smallfly',
  age: '27',
  toString() {
    console.log('[toString]');
    console.log('name: ' + this.name);
    return this.name;
  },
  valueOf() {
    console.log('[valueOf]');
    console.log('age: ' + this.age);
    return this.age;
  }
}

console.log(`${user}`); // [toString] name: Smallfly
console.log(+user); // [valueOf] age: 27 -- 27
console.log(user + 1); // [valueOf] age: 27 -- 27
複製代碼

從結果上看, toString/valueOf 的組合功能和 Symbol.toPrimitive 徹底同樣。

Symbol.toPrimitive 是 ES6 引入的新功能,實現了對象轉值類型方法的統一。

對象若是沒有實現這兩個方法,默認使用 Object 對象的 toString()/valueOf() 方法, toString() 方法返回 [object Object]valueOf() 方法返回對象自身。

const obj = {};
obj.toString(); // [object Object]
obj.valueOf() === obj; // true
複製代碼

[] + [] = ''

[] + [] 表達式的結果爲空字符串,咱們經過這個結果觀察一下對象轉值的具體過程。

const a = [];

a.toString = function() {
  return 'array string';
}

a.valueOf = function() {
  return 123;
}

console.log( `${a}` ); // array string
console.log(+a); // 123
複製代碼

以上代碼證實空數組 [] 沒有實現 Symbol.toPrimitive 方法,否則 toString/valueOf 方法不會被調用。

const a = [];

a[Symbol.toPrimitive] = function(hint) {
  console.log(hint);
  return 123;
}

console.log(a + a);
// default
// default
// 246
複製代碼

以上代碼證實 [] 參與加法運算產生的 hint 爲 default

根據前面提到的類型轉換的規則,[] + [] 表達式觸發 [] 轉值類型會依次調用 valueOf()toString() 方法。

然而,[].valueOf() 返回值是空數組自身,並非值類型,所以繼續嘗試調用 toString() 方法。

[].toString() 的結果是空字符串 '' 。所以,[] + [] 表達式的計算結果是 ''

參考連接

相關文章
相關標籤/搜索