關於強制類型轉換是一個設計上的缺陷仍是有用的特性,這一爭論從JavaScript 誕生之日起就開始了。在不少的 JavaScript 書籍中強制類型轉換被說成是危險、晦澀和糟糕的設計。數組
然而在不少實際應用中都運用了大量的弱類型轉換:app
alert({}); // 彈框提示:[object Object]
"0" + 1; // "01"
複製代碼
因爲這一特性,誕生了一系列有意思的題:函數
[] + {}; // "[object Object]"
{} + []; // 0
複製代碼
if (a == 1 && a == 2) {
alert('hello world!'); // 他是可能的
}
複製代碼
JavaScript的類型分爲原始類型與引用類型。ui
咱們能夠用 typeof 運算符來查看值的類型,它返回的是類型的字符串值。this
typeof undefined === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true
// ES6中新加入的類型
typeof Symbol() === "symbol"; // true
複製代碼
你可能注意到 null 類型不在此列。它比較特殊, typeof 對它的處理有問題:編碼
typeof null === "object"; // true
複製代碼
這個 bug 由來已久,在 JavaScript 中已經存在了將近二十年,也許永遠也不會修復,由於這牽涉到太多的 Web 系統,「修復」它會產生更多的bug,令許多系統沒法正常工做。spa
再來看看數組。JavaScript 支持數組,那麼它是否也是一個特殊類型?prototype
typeof [1,2,3] === "object"; // true
複製代碼
不,數組也是對象。確切地說,它也是 object 的一個「子類型」設計
最後看一組code
typeof false; // "boolean"
typeof new Boolean(false); // "object"
typeof 1; // "number"
typeof new Number(1); // "object"
複製代碼
將值從一種類型轉換爲另外一種類型一般稱爲類型轉換。 類型轉換又分爲隱式
與顯式
,如:
var a = 42;
var b = a + ""; // 隱式強制類型轉換
var c = String( a ); // 顯式強制類型轉換
複製代碼
ES5 規範第 9 節中定義了一些「抽象操做」(即「僅供內部使用的操做」)和轉換規則。即:
這些操做都是由js內部實現,並不是實際的函數。接下來咱們進行解析其內部實現。
將 引用類型值 強制類型轉換爲 string、number、boolean 是經過 ToPrimitive 抽象操做來完成的,咱們在此略過,稍後會詳細介紹。
抽象操做 ToString ,它負責處理非字符串到字符串的強制類型轉換。
基本類型值的字符串化規則爲: null 轉換爲 "null" , undefined 轉換爲 "undefined" , true 轉換爲 "true" 。數值遵循常規規則。
有時咱們須要將非數字值看成數字來使用,好比數學運算。爲此 ES5 規範在 9.3 節定義了抽象操做 ToNumber 。
其中 true 轉換爲 1 , false 轉換爲 0 。 undefined 轉換爲 NaN , null 轉換爲 0 。
ToNumber操做字符串時,會判斷該字符是否符合數字值格式。處理失敗時返回 NaN。其處理規則與Number函數類似:
Number( "" ); // 0
Number( "a1" ); // NaN
複製代碼
JavaScript 中有兩個關鍵詞 true 和 false ,分別表明布爾類型中的真和假。咱們常誤覺得數值 1 和 0 分別等同於 true 和 false 。在有些語言中多是這樣,但在 JavaScript 中布爾值和數字是不同的。
假值的布爾強制轉換的結果均爲 false 。
假值列表之外的值都是真值。即布爾強制轉換後均爲true。 因此空數組[]、空對象{}、字符串"null"、字符串"undefined" 等都爲真值。
抽象操做 ToPrimitive ,它負責將引用類型值轉化爲基本類型值。其具體規則爲: 一、檢查該值是否有 valueOf() 方法 二、若是有而且返回基本類型值,就使用該值進行強制類型轉換 三、若是沒有就使用 toString() 的返回值(若是存在)來進行強制類型轉換。 四、若是 valueOf() 和 toString() 均不返回基本類型值,會產生 TypeError 錯誤。
var a = {
valueOf: function(){
return "42";
}
};
Number( a ); // a.valueOf() -> "42" -> 42
String( a ); // a.toString() -> "[object Object]"
var b = {
toString: function(){
return "42";
}
};
Number( b ); // b.valueOf() -> {}; b.toString() -> "42" -> 42
String( b ); // b.toString() -> "42"
var c = [4,2];
c.toString = function(){
return this.join( "" ); // "42"
};
Number( c ); // c.toString() -> "42" -> 42
Number( [4, 2] ); // [4,2].toString() -> "4,2" -> NaN
複製代碼
a + "" (隱式)和前面的 String(a) (顯式)之間有一個細微的差異須要注意:
var a = {
valueOf: function() { return 42; },
toString: function() { return 4; }
};
a + ""; // a.valueOf() -> 42 -> "42"
String( a ); // a.toString() -> 4 -> "4"
複製代碼
函數:
操做符:
隱式強制類型轉換指的是那些隱蔽的強制類型轉換,反作用也不是很明顯。換句話說,你本身以爲不夠明顯的強制類型轉換均可以算做隱式強制類型轉換。
主要分爲:
這裏主要講算式與弱相等。
數字 + 字符串 number + string = ToString(number) + string
1 + '2'; //
複製代碼
數字 + 布爾 number + boolean = number + ToNumber(boolean)
1 + true; // 2 + ToNumber()
複製代碼
字符串 + 布爾 string + boolean = string + ToString( boolean )
'1' + true; // "1true"
複製代碼
原始類型 -x÷ 原始類型 primitive -x÷ primitive = ToNumber(primitive) -x÷ ToNumber(primitive)
引用類型在參與計算時,會先通過ToPrimitive轉換成原始類型,而後按照上述規則參與計算。
1 + {}; // "1[object Object]"
({toString(){return 1}}) + ({toString(){return 1}}); // 2
({toString(){return 1}}) + ({toString(){return '1'}}); // "11"
複製代碼
抽象相等也稱爲寬鬆相等,它的規則正是隱式強制類型轉換被詬病的緣由,很容易致使 bug,實際上他的規則很是簡單。
首先,有幾個很是規的狀況須要注意。
== 在比較兩個不一樣類型的值時會發生隱式強制類型轉換,會將其中之一或二者都轉換爲相同的類型後再進行比較。
這裏用字符串和數字的例子來解釋 == 中的強制類型轉換:
var a = 42;
var b = "42";
a === b; // false
a == b; // true
複製代碼
由於沒有強制類型轉換,因此 a === b 爲 false , 42 和 "42" 不相等。
而 a == b 是寬鬆相等,即若是兩個值的類型不一樣,則對其中之一或二者都進行強制類型轉換。
具體怎麼轉換?是 a 從 42 轉換爲字符串,仍是 b 從 "42" 轉換爲數字?
ES5 規範 11.9.3.4-5 這樣定義: (1) 若是 Type(x) 是數字, Type(y) 是字符串,則返回 x == ToNumber(y) 的結果。 (2) 若是 Type(x) 是字符串, Type(y) 是數字,則返回 ToNumber(x) == y 的結果。
其中ToNumber 爲抽象操做的規則前面已經介紹過。
== 最容易出錯的一個地方是 true 和 false 與其餘類型之間的相等比較。 例如:
var a = "42";
var b = true;
a == b; // false
複製代碼
咱們都知道 "42" 是一個真值(見本章前面部分),爲何 == 的結果不是 true 呢?緣由既簡單又複雜,讓人很容易掉坑裏,不少 JavaScript 開發人員對這個地方並未引發足夠的重視。
規範 11.9.3.6-7 是這樣說的: (1) 若是 Type(x) 是布爾類型,則返回 ToNumber(x) == y 的結果 (2) 若是 Type(y) 是布爾類型,則返回 x == ToNumber(y) 的結果。
根據規則 "42" == true ,通過ToNumber(true) -> 1; "42" == 1,再通過ToNumber("42") -> 42; 42 == 1,因此輸出false。
null 和 undefined 之間的 == 也涉及隱式強制類型轉換。
ES5 規範 11.9.3.2-3 規定: (1) 若是 x 爲 null , y 爲 undefined ,則結果爲 true 。 (2) 若是 x 爲 undefined , y 爲 null ,則結果爲 true 。
在 == 中 null 和 undefined 相等(它們也與其自身相等),除此以外其餘值都不存在這種狀況。 也就是說除null和undefined外的全部類型值都不與他們抽象相等。
var a = null;
var b;
a == b; // true
a == null; // true
b == null; // true
a == false; // false
b == false; // false
a == ""; // false
b == ""; // false
a == 0; // false
b == 0; // false
複製代碼
關於引用類型(對象 / 函數 / 數組)和原始類型值(字符串 / 數字 / 布爾值)之間的相等比較。
ES5 規範 11.9.3.8-9 作以下規定: (1) 若是 Type(x) 是字符串或數字, Type(y) 是對象,則返回 x == ToPrimitive(y) 的結果; (2) 若是 Type(x) 是對象, Type(y) 是字符串或數字,則返回 ToPromitive(x) == y 的結果。
例如
var a = 42;
var b = [ 42 ];
a == b; // true
複製代碼
[ 42 ] == 42, 通過ToPrimitive([ 42 ]) -> [ 42 ].toString() -> "42"; "42" == 42, 再通過ToNumber("42") -> 42; 42 == 42; 返回true
Number.prototype.valueOf = function() {
return 3;
};
new Number( 2 ) == 3; // true
複製代碼
而 2 == 3 不會有這種問題,由於 2 和 3 都是數字基本類型值,不會調用 Number.prototype.valueOf() 方法。而 Number(2) 涉及 ToPrimitive 強制類型 轉換,所以會調用 valueOf() 。
還有文章開頭的題:
if (a == 1 && a == 2) {
alert('hello world!');
}
複製代碼
你也許以爲這不可能,由於 a 不會同時等於 1 和 2 。但「同時」一詞並不許確,由於 a == 1 在 a == 2 以前執行。 若是讓 a.valueOf() 每次調用都產生反作用,好比第一次返回 1 ,第二次返回 2 ,就會出現這樣的狀況。
var i = 1;
Number.prototype.valueOf = function() {
return i++;
};
var a = new Number( 42 );
if (a == 1 && a == 2) {
console.log( "Yep, this happened." );
alert('hello world!');
}
複製代碼
再次強調,千萬不要這樣,也不要所以而抱怨強制類型轉換。對一種機制的濫用並不能成爲詬病它的藉口。咱們應該正確合理地運用強制類型轉換,避免這些極端的狀況。
本文介紹了 JavaScript 的數據類型之間的轉換:包括顯式和隱式。
在處理強制類型轉換的時候要十分當心,尤爲是隱式強制類型轉換。在編碼的時候,要知其然,還要知其因此然,並努力讓代碼清晰易讀。
本文大部分例子與描述來自 《你不知道的JavaScript》。