1. 程式人生 > 其它 >關於js中的隱式型別轉換

關於js中的隱式型別轉換

技術標籤:js/jqjavascript

  • 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中有兩種特殊情況無視規則:

  1. null == undefined;
  2. NaN和誰都不相等,包括他自己
console.log(undefined==null);		//true

其他值型別進行比較的時候都會將運算數轉換為數字

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() 轉成數字再進行比較
console.log("2" > "10");        //true    

在這裡面,‘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是不相等的

console.log(NaN === NaN); 		//false

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. 複雜資料型別的隱式轉換

複雜資料型別在隱式轉換時的順序如下:

  1. 先試用 valueOf() 方法獲取其原始值,如果原始值不是 number 型別,則使用 toString() 方法轉換成 string 型別。
  2. 將 string 型別轉換成 number 型別進行運算。
console.log([1,2]=='1,2');		//true

將 [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]

console.log([] == 0);       //true  

[] 通過同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