隱式轉換規則
前言:
涉及隱式轉換最多的兩個運算子 + 和 ==。
+運算子即可數字相加,也可以字串相加。
== 不同於===,故也存在隱式轉換。
減、乘、除這類運算子只會針對number型別,故轉換的結果就是轉換成number型別。
轉換表
規則:
第一步:決定該轉換成什麼類型:
加號(+)專屬規則:
- 當一側為String類型,加號(+)會被定義為字元串接,並會讓另一側優先轉換為字串。
- 當一側為Number類型,另一側為原始類型(primiary type),原始類型會轉換為Number型。
- 當一側為Number類型,另一側為引用類型(reference type:Array , Function , Object ),引用類型(一側)和Number型(另一側)皆轉換成字串後串接。
優先順序為由上而下!
第二步:需轉換側的值該如何轉。
- 將值轉為原始值,ToPrimitive():
js引擎內部的抽象操作ToPrimitive有著這樣的簽名:ToPrimitive(input, PreferredType?)
input是要轉換的值,PreferredType是可選引數,可以是Number或String型別,由第一步決定。
2-1. PreferredType是Number型(第一步決定該轉換為Number型)時:
- 如果輸入的值已經是一個原始值,則直接返回它
- 否則,如果輸入的值是一個物件,則呼叫該物件的valueOf()方法,如果valueOf()方法的返回值是一個原始值,則返回這個原始值。
- 否則,呼叫這個物件的toString()方法,如果toString()方法返回的是一個原始值,則返回這個原始值。
- 否則,丟擲TypeError異常。
2-2. PreferredType是String型(第一步決定該轉換為String型)時:
- 如果輸入的值已經是一個原始值,則直接返回它
- 否則,呼叫這個物件的toString()方法,如果toString()方法返回的是一個原始值,則返回這個原始值。
- 否則,如果輸入的值是一個物件,則呼叫該物件的valueOf()方法,如果valueOf()方法的返回值是一個原始值,則返回這個原始值。
- 否則,丟擲TypeError異常。
2-3. PreferredType在第一步沒有辦法指定轉換哪一種型時:
3-1.valueOf()方法:
- Number、Boolean、String這三種建構函式生成的基礎值的物件形式,通過valueOf轉換後會變成相應的原始值。
- Date這種特殊的物件,其原型Date.prototype上內建的valueOf函式將日期轉換為日期的毫秒的形式的數值。
- 除此之外返回的都為this,即物件本身。
var num = new Number('123');
num.valueOf(); // 123
var str = new String('12df');
str.valueOf(); // '12df'
var bool = new Boolean('fd');
bool.valueOf(); // true
var a = new Date();
a.valueOf(); // 1515143895500
var a = new Array();
a.valueOf() === a; // true
var b = new Object({});
b.valueOf() === b; // true
3-2.toString:
- Number、Boolean、String、Array、Date、RegExp、Function這幾種建構函式生成的物件,通過toString轉換後會變成相應的字串的形式。
- 其他物件返回的都是該物件的型別。
- 參考轉換表。
第三步:原始值轉換。
經過前面步驟,會得到一個原始值,之後依照運算子的需求選擇利用ToNumber將該原始值轉換成數自型態;或是利用ToString將該原始值轉換為字串,轉換規則參考頂層的轉換表。
試解:
第一題:
({} + {}) = ?
兩個物件的值進行+運算子,肯定要先進行隱式轉換為原始型別才能進行計算。
- 第一步:決定該轉換成什麼類型:因為不屬於+號的3個條件,屬於2-3.之情況。
1、第二步:ToPrimitive轉換,由於沒有指定PreferredType型別,{}會使預設值為Number,進行ToPrimitive(input, Number)運算。
2、所以會執行valueOf方法,({}).valueOf(),返回的還是{}物件,不是原始值。
3、繼續執行toString方法,({}).toString(),返回"[object Object]",是原始值。
故得到最終的結果,"[object Object]" + "[object Object]" = "[object Object][object Object]"
第二題:
2 * {} = ?
1、首先乘號運算子只能對number型別進行運算,故第一步就是對{}進行ToNumber型別轉換。
2、由於{}是物件型別,故先進行原始型別轉換,ToPrimitive(input, Number)運算。
3、所以會執行valueOf方法,({}).valueOf(),返回的還是{}物件,不是原始值。
4、繼續執行toString方法,({}).toString(),返回"[object Object]",是原始值。
5、轉換為原始值後再進行ToNumber運算,"[object Object]"就轉換為NaN。
故最終的結果為 2 * NaN = NaN
嚴格相等(===)
定義:比較兩側的值是否在型態上及數值上皆相同。
NaN === NaN; //false
+0 === -0; //true
寬鬆相等(==)
定義:寬鬆相等不限於型態相同,即型態不相同也可寬鬆相等。
1 == '1' // true
運作方式:
- 一側為boolean值的寬鬆相等比較:
值首先會被轉換為數字類型,根據boolean類型的ToNumber規則,true轉為1,false轉為0。
- 數字類型和字串類型的寬鬆相等比較:
當數字類型和字串類型做寬鬆相等比較時,字串類型會被轉換為數字類型;根據字串的ToNumber規則,如果是純數字形式的字符串,則轉為對應的數字,空字符轉為0, 否則一律按轉換失敗處理,轉為NaN。
注意NaN不與任何值相等,包括它自己。
注意字串內如果是16進位制,也算純數字。
- 當引用型別和原始型別的寬鬆相等比較:
對象類型會依照ToPrimitive規則轉換為原始類型
試解:
第一題:
[2] == 2 // true
解:
- 屬於引用型別和原始型別的寬鬆相等比較。
- [2]做ToPrimitive操作,因為[2]非data型(2-3.),所以PreferredType是Number型
- [2]也就是先調用valueOf,回傳仍是[2]。
- [2]再調用toString,回傳是'2'。
- '2'已經符合數字類型和字串類型的寬鬆相等比較之條件,ToNumber變成2。
第二題:
[] == !{} // true
1、! 運算子優先順序高於==,故先進行!運算。
2、!{}運算結果為false,結果變成 [] == false比較。
3、屬於一側為boolean值的寬鬆相等比較:
等式右邊y = ToNumber(false) = 0。結果變成 [] == 0。
4、屬於當引用型別和原始型別的寬鬆相等比較:
[]會先呼叫valueOf函式,返回this。
不是原始值,繼續呼叫toString方法,x = [].toString() = ''。
故結果為 '' == 0。
5、屬於數字類型和字串類型的寬鬆相等比較:
等式左邊x = ToNumber('') = 0。
所以結果變為: 0 == 0,返回true,比較結束。
參考文章:
- IT人-javascript 隱式轉換:https://iter01.com/472967.html
- 程式前沿-從一道面試題說起—js隱式轉換踩坑合集:https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/701003/#outline__1
- JavaScript 隐式类型转换,一篇就够了!:https://chinese.freecodecamp.org/news/javascript-implicit-type-conversion/