在Js中, 強制類型轉化分為兩種情況: 一種是引用類型轉化基本類型, 如數組轉化成數字;一種是兩種不同基本類型之間的轉化,如字符串轉化為數字。你不能將基本類型轉化成引用類型,比如,不可能把數字轉化為數組。 基本類型之間的轉化相對容易,引用類型轉化為基本類型則要復雜的多,轉化又分為兩種情況,轉化為字符號或轉化為數值
當引用類型轉化為字符串的時候,JS會先調用引用類型的toString 方法,看它能不能返回基本類型,如果能,則使用該基本類型,如果不能,則再調用引用類型的valueOf()方法,如果valueOf方法能返回基本數據類型則使用該基本數據類型 ,如果不能,就會拋出TypeError錯誤。你可能還記得 字符串 "[object object]"
var arr = [5, 5]; console.log(arr + ''); // "5, 5" 調用數組的toString()方法
當引用類型轉化為數值的時候,JS則先調用引用類型的valueOf()方法,同樣是看它能不能返回基本類型,如果能就用,如果不能,則繼續調用toString()方法,如果toString()方法也不能返回基本類型,則拋出TypeError錯誤。
console.log(+ new Date()); // 時間毫米數1513860979488, 數值類型
知道了轉化規則,我們就可以利用它來轉化我們自定義的對象,但有的時候,轉化並沒有我們上面看到的那么簡單,看下面的代碼
var Money = function(val, sym) { this.currentSysmbol = sym; this.cents = val; } var dollar = new Money(100, '$'); console.log(+dollar); // NaN console.log("Total" + dollar); // '[object, object]'
得到的結果並沒有什么意義,我們來給它提供toString, valueOf()方法
Money.prototype.toString = function() { return this.currencySymbol + (this.cents / 100).toFixed(2); } Money.prototype.valueOf = function() { return this.cents } console.log(+dollar); // number 100 console.log("" + dollar); // string 100
這時你會發現一個問題: " " +dollar, 按理說,它應該調用toString 方法,返回$1.00, 但它卻返回了100, 證明它這里調用的是valueOf()方法,而不是toStirng()方法,這和我們的規則出現了不一致。 但是當我們把dollar 用數組包起來的時候,它返回調用的是toString()方法
console.log("" + [dollar]) // $1.00
這時要看一下js的標准,
轉化成基本類型的抽象方法ToPrimitive 接受一個必選的參數(input argument) ,就是一個引用類型object和一個可選的參數(PreferredType), 然后把引用類型轉化成基本類型。如果一個引用類型能夠返回不止一個基本類型時,它就可能需要PreferredType 來決定返回哪一個。這個方法是怎么把引用類型轉化成基本類型的?
轉化成基本類型,就是標准中所說的return a default value, 需要調用引用類型內置的方法,然后給它傳遞可選的參數(hint PreferredType). 如果再深入挖掘標准, 你會發現所說的方法,就是toString()和valueOf(), 返回字符串或數值, 到底是返回哪種基本類型取決於傳入的可選參數hint PreferredType. 如果hint參數沒有傳遞的話,它默認返回數值number。請下面的標准
這也就解釋了("" + dollar)為什么沒有調用我們定義的toString()方法,而是調用valueOf()方法了, 因為我們沒有提供hint PreferredType參數,默認返回了數值。基本操作符並不能直接用來計算引用類型,它只能計算基本類型, 當計算引用類型的時候,它進行強制類型轉化,而這種轉化到底轉化成哪種基本類型,是js自己做決定。那我們能不能提供一個hint Preferred 參數,把決定權拿回到我們手里。很不幸,js並不沒有提供這樣的機制,也就是說,我們不能提供一個hint 參數給我們自己定義的對象
但是當我們把dollar用數組括起來,它卻調用了我們自定義的toString()方法. "" + [dollar] 返回 $1.00, 這又怎么解釋呢? 這涉及了Js的內置對象。
對於內置對象的來說, 它必須返回一個基本類型。我們把dollar用數組包括起來,但由於涉及到加號操作,所以[dollar] 要轉化成基本類型,根據規則,它調用的valueOf()方法,但是數組的valueOf方法返回數組本身,不是基本類型。所以再調用數組的toString()方法,數組的toString()方法就是數組的每一項都調用toString()方法,然后再拼接起來,形成一個大的字符串,所以這里調用的是dollar 對象的toString()方法。
我們可以模擬一個ToPrimitive的方法
var ToPrimitive = function(obj) { var funct, functions, val, _i, _length; functions = ['valueOf', 'toString']; if(typeof obj === 'object') { // js 對Date日期對象作了區別處理, 先調用toString, 再調用toValue if (obj instanceof Date) { functions = ['toString', "valueOf"]; } for(_i = 0, _length=functions.length; _i < _length; _i++){ funct = functions[_i]; // valueof 或 toString 方法都是函數時才調用 if (typeof obj[funct] === 'function') { val = obj[funct](); if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') { return val; } } } // 如果兩個方法都不能轉化成基本數據類型,則拋出錯誤 throw new Error('DefualtValue is ambuiguos') } return obj; // 如果傳遞進來的參數不是引用類型,則返回 }
現在我們已經明白了js的類型轉化,再做一個復雜的例子加深一下印象, 下面這行代碼輸出多少
console.log(++[[]][+[]]+[+[]])
1, 先計算數組內的元素+[], 前面的加號肯定是要數組轉化成基本類型,數組轉化成基本類型,只能調用它的toString()方法,空數組轉化為空字符串'', +'' 表示對空字符串轉化為數字,它這里調用的是Number()函數,所為轉為化0, 現在變成了
console.log(++[[]][0]+[0])
2, 加號左邊[[]][0], 表示它取的是[[]] 里面的第0個位置的元素,[[]] 表示它是一個二維數組,數組里面包括數組,所以它的第0個位置上的元素還是數組,變成了
console.log(++[]+[0])
3 , 左邊是++[], 還是對數組進行基本類型轉化,上面說了 [] 轉化為空字符串,++‘’ 則變成了數字1,
console.log(1+[0])
4, 右側還是要做類型轉化, 數組[0] 調用toString,則變成了字符串‘0’
console.log(1+'0')
5, 最終的結果就是字符串‘10’