記不清在某處看見了這一比較,當時對強制轉換這塊理解的還沒有特別清晰,故有此一文。以為我會以標題的表達式來展開?那你就錯了,下麵直接上[] == ![]是如何轉換的: 1. 因為!運算符的優先順序比較高,所以表達式右側先運行![],得出false,表達式變為[] == false 2. 強制將false ...
記不清在某處看見了這一比較,當時對強制轉換這塊理解的還沒有特別清晰,故有此一文。以為我會以標題的表達式來展開?那你就錯了,下麵直接上[] == []是如何轉換的:
- 因為!運算符的優先順序比較高,所以表達式右側先運行[],得出false,表達式變為[] == false
- 強制將false轉換為0,表達式變為[] == 0
- 將[]強制轉換為原始類型後為"",表達式變為"" == 0
- 將""轉換為Number類型,表達式變為0 == 0
兩側類型相同,直接返回0 === 0的結果true
- - -前言
本文旨在總結js中強制轉換的規則及觸發強制轉換的幾種場景。ES6標準中定義了六種原始類型,分別是Undefined,Null,String,Number,Boolean,Symbol。本文中的強制轉換指的是在代碼運行時,觸發了數值的隱式轉換,而不是代碼顯示的指定轉換操作。
原始類型間強制轉換
發生在原始類型之間的轉換,以個人的理解是其他類型轉換為String,Number或者Boolean類型。
轉換為String類型
其他原始類型轉換為String類型通常發生在+兩邊存在字元串時,會將+另一邊的值轉換為String類型。
考慮如下代碼:
var strAddNum = "test" + 1;
var numAddStr = 1 + "test";
var boolAddStr = true + "test";
var undAddStr = undefined + "";
var nullAddStr = null + "";
console.log(strAddNum);
console.log(numAddStr);
console.log(boolAddStr);
console.log(undAddStr);
console.log(nullAddStr);
代碼傳送門,以上代碼的運行結果均為字元串。其他原始類型轉換為String類型基本是其值的字元串形式,具體如下:
- Undefined,"undefined"
- Null,"null"
- Boolean,"true"或"false"
- Number,值為NaN,"NaN"
- Number,值為+0或-0,"0"
- Number,值為+Infinity,"Infinity"
- Number,值為-Infinity,"-Infinity"
Number轉為字元串具體可參考ES2018 7.1.12.1章節
註意:Symbol類型無法轉換為String類型。
轉換為Number類型
轉換為Number類型的情況,+-*/%等運算中,除了+之外其他運算均會轉換成Number類型,+運算時需要滿足兩側未出現String類型,該值才會被轉換為Number類型。+運算時情況較為複雜,後面會專門描述其相關轉換規則。考慮如下代碼:
var trueAddTrue = true + true;
var trueAddFalse = true + false;
var trueAdda0 = true + 0;
var nullAddTrue = null + true;
var undefinedAdd0 = undefined + 0;
var strAdd0 = "" + 0;
console.log(trueAddTrue);
console.log(trueAddFalse);
console.log(trueAdda0);
console.log(nullAddTrue);
console.log(undefinedAdd0);
console.log(strAdd0);
代碼傳送門,在運行代碼之前可以先考慮下以上代碼答列印的結果分別是什麼?然後再運行,看是否符合你的預期。其他原始類型轉換為Number類型的具體如下:
- Undefined,NaN
- Null, +0
- Boolaen,值為true,1
- Boolean,值為false,+0
- String,不可轉為Number的,NaN
- String,可轉為Number的就是其對應的Number值(具體可參考ES2018 7.1.3.1)
註意:Symbol類型同樣無法轉換為Number類型。
轉換為Boolean類型
轉換為Boolean類型的情況較為簡單,除了以下情況轉換為Boolean類型會是false,其他情況均是true
- Undefined
- Null
- Number,+0,-0,NaN
- String,長度為0的字元串
這幾種false的情況在ES標準中有明確規定7.1.2
對象強制轉換為原始類型
ES中將對象轉換為原始類型的演算法,大致可描述為三種情形:
- 如果該對象設置了[Symbol.toPrimitive],調用該函數,如果其返回值為非Object類型則返回結果,否則拋出TypeError異常
- 若未指定轉換提示則轉換提示為"default"
- 若轉換提示為"default",則將其置為"number"
- 當指定轉換提示為"number"時先調用該對象的valueOf函數並判斷其結果,如果是原始類型則返回結果,否則調用該對象的toString函數並判斷其返回結果,如果結果為原始類型則返回,否則拋出異常TypeError
- 當指定轉換提示為"string"時先調用toString函數並判斷其返回結果,如果是原始類型則返回結果,否則調用該對象的valueOf函數並判斷其返回結果,如果結果為原始類型則返回,否則拋出異常TypeError
上述三種情形中第一種情形優先順序最高,第二三種情形優先順序併列,具體需要根據使用場景判斷是哪一種。其中的指定轉換提示是ES標準內部調用該演算法時指定的。
第一種情形只有Symbol對象和Date對象內置了[Symbol.toPrimitive],且該屬性的writeable為false,enumerable為false,configurable為true
對象轉換為原始類型時發生的強制轉換非特殊情況均為第二種,第三種情況較為少見。在正常編碼工作中應該使用第二種情形就夠用了,第三種情形幾乎不會出現,要瞭解更多細節可查閱ES標準。
var test = {
[Symbol.toPrimitive]: function(hint) {
console.log(hint)
},
valueOf: function() {
console.log("valueOf")
},
toString: function() {
console.log("toString")
}
}
test + ""; //"default"
test * 0; //"number"
String(test); //"string"
代碼傳送門上述代碼指定了分別指定了test對象的[Symbol.toPrimitive],valueOf和toString函數,可以觀察到並valueoOf和toString函數均未被調用,指定的[Symbol.toPrimitive]函數可以接受一個提示參數,這個參數就是強制轉換時的強制轉換提示。這樣我們在函數中就可以根據轉換場景的不同分別返回不同的值。
原始類型強制轉換為對象(裝箱)
在開始描述這個問題之前,可以先思考一下,都有哪些場景會是強制的將原始類型轉換為對象,其實這種場景幾乎在js代碼中隨處可見,考慮如下代碼:
var str = "testString";
str.replace("test", "");
如上代碼中定義的str的值並不是一個對象而是一個原始類型String,原始類型顯然是沒有方法可以調用的。
實際上這裡的str在執行str.replace時str其值會被強制轉換為對象,得到一個String類型的實例對象,而該實例的原型上定義了一系列方法,且該實例是無法被獲取的,在執行完這行代碼後,該實例就會被回收,所以這裡的str依然是一個字元串。
考慮如下代碼:
var a = 3;
a.fn = function(){};
a.fn();
強制轉換的幾種場景
在js代碼中會出現強制轉換的場景通常有三種:
- +運算
- -,*,/,%運算
- ==比較
作為判斷條件
+運算
一元+運算
做一元+運算時,均會被強制轉為Number類型,例如
var a = {
[Symbol.toPrimitive]: function(hint) {
console.log(hint); // number
if(hint === "number") {
return 2;
} else {
return 9;
}
}
};
console.log(+a); // 2
var b = "3";
console.log(+b); // 3
二元+運算
二元+運算為幾種強制轉換中複雜度僅次於==比較的一種情形,個人總結其轉換步驟如下:
- 先將兩側數值強制轉換為原始類型(未指定轉換提示,即轉換提示為hint default);
- 若兩側存在String類型,均轉換為String類型,則返回兩側拼接的字元串;
- 若第2未返回,則兩側數值強制轉換為Number類型,返回計算結果;
var a = "";
var b = {
[Symbol.toPrimitive]: function(hint) {
console.log(hint); // "default"
if(hint === "default") {
return 2;
} else {
return 9;
}
}
};
var c = a + b; //這裡b轉換為原始類型返回的是Number類型2,由於a是"",所以b被轉換為"2",後與""拼接返回"2"
console.log(c); // "2"
var d = 3;
var e = {
[Symbol.toPrimitive]: function(hint) {
console.log(hint); // "default"
if(hint === "default") {
return 2;
} else {
return 9;
}
}
};
var f = d + e; //這裡e轉換為原始類型返回的是Number類型2,由於兩側均沒有String類型,則至第3步,強制轉換為Number後返回兩側相加的結果5
console.log(f); // 5
-,*,/,%運算
這幾個運算符這涉及的強制轉換都是轉換為Number類型的,所以這裡只要搞清楚轉換為Number是怎樣的過程就可以了。上文中已經對原始類型轉換為Number類型做了描述,這裡補充一下Object轉換為Number的過程:
- 將對象轉換為原始類型,且轉換時會指定轉換提示為"number";
- 轉換為原始類型後再根據原始類型轉換為Number類型進行轉換;
var a = 8;
var b = {
[Symbol.toPrimitive]: function(hint) {
console.log(hint); // "number"
if(hint === "number") {
return 2;
} else {
return 9;
}
}
};
console.log(a-b); // 6
console.log(a/b); // 4
console.log(a*b); // 16
console.log(a%b); // 0
console.log(undefined * 0); //NaN
console.log(null * -1); // 0
console.log(false * -1); //0
console.log(true * -1); // -1
console.log("1" * -1); // -1
==比較
==比較的基礎===比較
x === y,其具體比較步驟如下:
- 若x和y的類型不一致,返回false;
- 若x和y為Number類型,則若x和y中有一個為NaN返回false;若x和y的值相等則返回true;若x是+0,y是-0或者x是-0,y是+0則返回true;其他情況返回false;
- 若x和y為Undefined類型,返回true
- 若x和y為Null類型,返回true
- 若x和y為String類型,其值相同則返回true,否則返回false
- 若x和y為Boolean類型,其值均為true或均為false返回true,否則返回false
- 若x和y為Symbol類型,其值為同一個Symbol值則返回true,否則返回false
- 若x和y為Object類型,其值為同一個對象(其引用地址相同)則返回true,否則返回false
x == y規則
==比較的轉換規則雖然稍微多一點,實際上也就幾條規則,兩側的數值類型符合哪種就按哪種去轉換,只不過有的可能需要轉兩次,具體如下:
- 如果兩側類型相等則直接返回===的結果;
- 若x為undefined和y為null或x為null和y為undefined,返回true
- 若兩側為String類型和Number類型,將String類型轉換為Number類型,繼續用==比較
- 若有一側存在Boolean類型,將Boolean類型轉換為Number類型,繼續用==比較
- 若兩側為String,Number或Symbol類型和Object類型,將Object類型轉換原始類型,繼續用==比較
- 其他返回false
下麵列舉一些可能有點違反直覺的比較
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
[] == ![]; //true
作為條件判斷
這種情形到沒有太多可說的,基本上就是,除了undefined,null,+0,-0,NaN,""這六個值會被轉為false,其他情況均為true;
出現將不是Boolean類型的值強制轉換的情況為
- if(...)
- for(...;...;...)第二個條件表達式
- while(...)和do...while(...)中的條件表達式
- ...?...:...三元表達式中的第一個條件表達式
- ||和&&
結論
其實上面描述了這麼多,日常開發環境中用到比較多的應該是作為判斷條件,+,==這三種情況了,這三種中最常見的應該是判斷條件的情況了,這種情況反而是最簡單的一種了。