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,分別是 number
、 string
、 default
。
咱們能夠根據 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
屬性的 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
轉簡單值類型的全部狀況。
若是對象沒有實現 Symbol.toPrimitive
方法,會繼續嘗試調用 toString
和 valueOf
方法。
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()
的結果是空字符串 ''
。所以,[] + []
表達式的計算結果是 ''
。
參考連接