-
2021-01-07
剛爬出資料類型判斷,又進了隐式類型轉換的大坑,那就根據日常會碰到的問題簡單寫一下吧。
-
2021-01-08
昨天寫到一半讓朋友們拉去雲喝酒了,今天繼續
一、開始
我最早接觸到隐式類型轉換是因為看到這麼一段代碼:
const a = {
i:1,
toString: function () {
return a.i++;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('hello world!');
}
當時我百思不得其解,為什麼這個 if 語句可以成立,還執行了列印語句。也就是因為這個題目,我慢慢了解到了隐式類型轉換這個問題。
二、為什麼會出現隐式類型轉換
- 在js中,當運算符在運算時,如果兩邊資料不統一,CPU就無法計算,這時我們編譯器會自動将運算符兩邊的資料做一個資料類型轉換,轉成一樣的資料類型再計算
- 這種無需程式員手動轉換,而由編譯器自動轉換的方式就稱為隐式轉換
三、運算符中的隐式類型轉換
在實際應用中,最常見的就是+或者==這種運算符導緻的隐式類型轉換,在操作過程中,系統沒辦法把不同類型的資料進行運算,是以他會自動進行類型轉換。
運算符中的隐式類型轉換主要分為以下幾種情況:
1. 轉換為數字類型
+、-、*、/、++、–、+=、-=、>、<、>=、<=、==、!= 這些運算中,系統都會将值先轉換為數字類型再進行計算或比較。
加号運算符( + )可以把任何資料類型轉換為數字,轉換規則與 Number() 方法相同。
Null、Undefined 和 false在與數字的計算中折算為0,true 折算為1。
undefined會被轉換為 NaN。
NaN在算術運算符中遇到任何值進行比較結果都為NaN。
console.log(1+'true'); //'1true'
console.log(1+true); //2
console.log(1+null); //1
console.log(1+undefined); //NaN
可以用加号運算符很友善的把其他類型的值轉換為數字類型。
console.log(+true); //1
console.log(+undefined); //NaN
console.log(+"123"); //123
console.log(+"string"); //NaN
除了加号運算符,也可以使用 a - 0 的形式,将資料類型轉換為數字。
console.log(11-'5'); //6
console.log('11'-'5'); //6
console.log(-null); //-0
console.log(true - 0); //1
console.log(null - 0); //0
console.log("123" - 0); //123
console.log("" - 0); //0
console.log([999] - 0); //999
console.log([2] - [1]); //1
比較運算符(= =),== 不同于===,故也存在隐式轉換。
比較運算符會把其他資料類型轉換number資料類型後再比較。
在javascript中有兩種特殊情況無視規則:
- null == undefined;
- NaN和誰都不相等,包括他自己
其他值類型進行比較的時候都會将運算數轉換為數字
console.log("3"==3); //true
console.log("1"==true); //true
console.log(false == 0); //true
console.log(false == ""); //true
console.log(NaN == NaN); //false
console.log(undefined == null); //true
比較運算符(>、<)中也存在隐式類型轉換。
console.log("2" > 10); //false
console.log("a" > 10); //false
console.log(10 > "a"); //false
但是最好不要這樣比較,因為會非常容易翻車
比較運算符的規則是這樣的:
- 比較運算符的一邊是字元串的時候,會調用 Number() 方法把字元串轉換成數字在進行比較
-
當關系運算符兩邊都是字元串的時候,此時同時轉成number然後比較關系。
重點:此時并不是按照Number()的形式轉成數字,而是按照字元串對應的unicode編碼來轉成數字,使用 字元串.charCodeAt(字元下标,預設為0) 方法可以檢視字元的unicode編碼。
- 布爾值和數字比較時,會把布爾值通過 Number() 轉成數字再進行比較,true轉成 1,false 轉成 0;
- 字元串和布爾值比較時,會把字元串和布爾值都通過 Number() 轉成數字再進行比較
在這裡面,‘2’ 的 Unicode 編碼是50,'10’的 Unicode 編碼是49,是以傳回的值為 true。
2. 轉換為字元串類型
+運算符既可數字相加,也可以字元串相加。
console.log(1+'2'); //12
console.log("12" + "3"); //"123"
console.log("12" + 3); //"123"
console.log(12+ 3); //15
console.log(1+'2'+ 3); //'123'
根據 ES5 規範,如果某個操作數是字元串或者能夠轉換為字元串的話,+ 将進行拼接操作。
其中,"能夠轉換為字元串"的操作主要是針對對象而言,當某個操作數是對象的時候,會首先檢查該對象能否通過 valueOf() 方法傳回基本類型值,如果不能,就轉而調用 toString() 方法。
通過以上兩個步驟,如果可以得到字元串,那麼+ 也将進行拼接操作,否則執行數字加法。
console.log("" + 1); //"1"
console.log("" + true); //"true"
console.log("" + null); //"null"
console.log("" + [3, 4]); //"3,4"
console.log("" + "string"); //"string"
console.log("" + undefined); //"undefined"
在進行拼接操作的時候,也會将另一個操作數隐式轉換為字元串。
這個隐式轉換和使用 String() 方法之間有一個細微差别需要注意:
隐式轉換會對操作數首先調用 valueOf() 方法,然後通過 toString() 方法将傳回值轉換為字元串,而使用 String() 方法則是直接調用 ToString()。
var o = {
valueOf: function() {
return 11;
},
toString: function() {
return 22;
}
};
console.log(o + ""); //輸出:"11"
console.log(String(o)); //輸出:"22"
3. 轉換為布爾類型
邏輯非運算符( ! )可以把任何資料類型轉換為布爾值,轉換規則與 Boolean() 方法相同,一般會連續用兩個歎号( !! ),因為第二個歎号會将結果反轉回原值。
- 0、-0、NaN、undefined、null、空字元串、false、document.all() 用邏輯非運算符的結果為false。
- 除了以上八種情況之外,所有資料都會得到 true 。
console.log(!!0); //false
console.log(!!-0); //false
console.log(!!""); //false
console.log(!![]); //true
console.log(!!{}); //true
console.log(!!null); //false
console.log(!!false); //false
console.log(!!NaN); //false
console.log(!!undefined); //false
四、語句中的隐式類型轉換
1. if 語句
if 語句會将值隐式轉換為Boolean類型
var obj = {name:'jack'}
if(obj){
//do something..
}
2. while 語句
while 語句和 if 語句的作用相同,将值隐式轉換為Boolean類型
var obj = {name:'jack'}
while(obj){
//do something..
}
五、常見問題(當心踩坑)
1. NaN的判斷錯誤
隐式類型轉換有時候是會隐藏一些錯誤的,比如,null會轉換成0,undefined會轉換成NaN。需要注意的是,NaN和NaN是不相等的
JavaScript本身提供判斷某個值是否為NaN的方法isNaN(),但是這個方法并不是非常準确,因為在調用這個方法的時候,他會有一個隐式類型轉換的動作将這個值轉換為數字類型,這樣就會導緻把原本不是NaN的值轉換成NaN。
console.log(isNaN({})); //true
console.log(isNaN("foo")); //true
console.log(isNaN(undefined)); //true
這個問題也非常好解決,因為NaN是一個特殊的值,隻有他自己不等于自己,我們利用這一點,可以自己寫一個方法來判斷:
function isRealNaN(x) {
return x !== x;
}
console.log(isRealNaN(NaN)); //true
console.log(isRealNaN({})); //false
console.log(isRealNaN("foo")); //false
console.log(isRealNaN(undefined)); //false
2. 複雜資料類型的隐式轉換
複雜資料類型在隐式轉換時的順序如下:
- 先試用 valueOf() 方法擷取其原始值,如果原始值不是 number 類型,則使用 toString() 方法轉換成 string 類型。
- 将 string 類型轉換成 number 類型進行運算。
将 [1,2] 用 valueOf() 取其原始值,發現不是數字類型,轉換成 string 類型之後,發現與 ‘1,2’ 相等,是以傳回 true。
var a = {};
console.log(a=="[object Object]"); //true
這些問題都是因為==的隐式類型轉換造成的。
console.log({} == 0); //false
console.log({} == []); //false
另外,空數組的toString()方法會得到空字元串,而空對象的toString()方法會得到字元串[object Object]
[] 通過同String()轉成空字元串,再通過Number()轉成0。
3. 邏輯非隐式轉換與關系運算符隐式轉換搞混淆
邏輯非運算符和關系運算符的原理上面都講過了,但是放在一起用的話非常非常容易踩坑:
console.log([]==0); //true
console.log(![]==0); //true
[] 與 0比較:
- [].valueOf().toString() 得到空字元串
- Number("") == 0 成立
![] 與 0比較:
- 邏輯非優先級高于關系運算符,![] = false (空數組轉布爾得到true,然後取反得到false)
- false == 0 成立
console.log([]==[]); //false
console.log([]==![]); //true
[] 與 []比較:
-
引用類型資料存在堆記憶體中,棧記憶體中存儲的是位址,是以他們的結果是false。
(我自己也不是很懂,看到别的大神是這麼解釋的,先抄下來吧…)
[] 與 ![]比較:
- [].valueOf().toString() 得到空字元串 “”
- ![] = false
- Number("") == Number(false) 成立 都是0
console.log({}=={}); //false
console.log({}==!{}); //false
{} 與 {}比較:
- 引用類型資料存在堆記憶體中,棧記憶體中存儲的是位址,是以他們的結果是false
{} 與 !{}比較:
- {}.valueOf().toString() 得到字元串’[object Object]’
- !{} = false
- Number(’[object Object]’) == Number(false) 不成立,因為轉換到最後 是NaN 和 0比較,是以結果為 false