JS 類型隱式轉換

1、js數(shù)據類型

js中有7種數(shù)據類型,可以分為兩類:原始類型、對象類型:

基礎類型(原始值):

Undefined、 Null、 String、 Number、 Boolean、 Symbol (es6新出的,本文不討論這種類型)
復雜類型(對象值):

object

2、三種隱式轉換類型

js中一個難點就是js隱形轉換,因為js在一些操作符下其類型會做一些變化,所以js靈活,同時造成易出錯,并且難以理解。

涉及隱式轉換最多的兩個運算符 + 和 ==。

+運算符即可數(shù)字相加,也可以字符串相加。所以轉換時很麻煩,有些也比較難理解:

[]+{}
"[object Object]"
{}+[]
"a"+[]
"a"
"a"+{}
"a[object Object]"
1+[]
"1"
1+{}
"1[object Obje
0
1+'0'
"10"
null+1
1
null+"q"
"nullq"
undefined+1
NaN
undefined+"b"
"undefinedb"
NaN+1
NaN
NaN+"c"
"NaNc"

== 不同于===,故也存在隱式轉換。

undefined==null
true
null==1
false
null==0
false
undefined==1
false
undefined==0
false
[1]==1
true
var a={1:1}
undefined
a==1
false
    • / 這些運算符只會針對number類型,故轉換的結果只能是轉換成number類型,比較簡單。

既然要隱式轉換,那到底怎么轉換呢,應該有一套轉換規(guī)則,才能追蹤最終轉換成什么了。

隱式轉換中主要涉及到三種轉換:

1、將值轉為原始值,ToPrimitive()。

2、將值轉為數(shù)字,ToNumber()。

3、將值轉為字符串,ToString()。

2.1、通過ToPrimitive將值轉換為原始值

js引擎內部的抽象操作ToPrimitive有著這樣的簽名:

ToPrimitive(input, PreferredType?)

input是要轉換的值,PreferredType是可選參數(shù),可以是Number或String類型。
他只是一個轉換標志,轉化后的結果并不一定是這個參數(shù)所值的類型,但是轉換結果一定是一個原始值(或者報錯)。

2.1.1、如果PreferredType被標記為Number,則會進行下面的操作流程來轉換輸入的值。

1、如果輸入的值已經是一個原始值,則直接返回它
2、否則,如果輸入的值是一個對象,則調用該對象的valueOf()方法,
如果valueOf()方法的返回值是一個原始值,則返回這個原始值。
3、否則,調用這個對象的toString()方法,如果toString()方法返回的是一個原始值,則返回這個原始值。
4、否則,拋出TypeError異常。

2.1.2、如果PreferredType被標記為String,則會進行下面的操作流程來轉換輸入的值。

1、如果輸入的值已經是一個原始值,則直接返回它
2、否則,調用這個對象的toString()方法,如果toString()方法返回的是一個原始值,則返回這個原始值。
3、否則,如果輸入的值是一個對象,則調用該對象的valueOf()方法,
如果valueOf()方法的返回值是一個原始值,則返回這個原始值。
4、否則,拋出TypeError異常。

既然PreferredType是可選參數(shù),那么如果沒有這個參數(shù)時,怎么轉換呢?

PreferredType的值會按照這樣的規(guī)則來自動設置:

1、該對象為Date類型,則PreferredType被設置為String

2、否則,PreferredType被設置為Number

2.1.3、valueOf方法和toString方法解析

上面主要提及到了valueOf方法和toString方法,那這兩個方法在對象里是否一定存在呢?

答案是肯定的。在控制臺輸出Object.prototype,你會發(fā)現(xiàn)其中就有valueOf和toString方法,而Object.prototype是所有對象原型鏈頂層原型,所有對象都會繼承該原型的方法,故任何對象都會有valueOf和toString方法。

對象的valueOf函數(shù),其轉換結果是什么?

對于js的常見內置對象:Date, Array, Math, Number, Boolean, String, Array, RegExp, Function。

1、Number、Boolean、String這三種構造函數(shù)生成的基礎值的對象形式,通過valueOf轉換后會變成相應的原始值。如:

var num = new Number('123');
num.valueOf(); // 123
var str = new String('12df');
str.valueOf(); // '12df'
var bool = new Boolean('fd');
bool.valueOf(); // true

2、Date這種特殊的對象,其原型Date.prototype上內置的valueOf函數(shù)將日期轉換為日期的毫秒的形式的數(shù)值。

var a = new Date();
a.valueOf(); // 1532234132475

3、除此之外返回的都為this,即對象本身:

var a = new Array();
a.valueOf() === a; 
// true
var b = new Object({});
b.valueOf() === b;
 // true

toString函數(shù),其轉換結果是什么?

對于js的常見內置對象:Date, Array, Math, Number, Boolean, String, Array, RegExp, Function。

1、Number、Boolean、String、Array、Date、RegExp、Function這幾種構造函數(shù)生成的對象,通過toString轉換后會變成相應的字符串的形式,因為這些構造函數(shù)上封裝了自己的toString方法。如:

Number.prototype.hasOwnProperty('toString'); // true
Boolean.prototype.hasOwnProperty('toString'); // true
String.prototype.hasOwnProperty('toString'); // true
Array.prototype.hasOwnProperty('toString'); // true
Date.prototype.hasOwnProperty('toString'); // true
RegExp.prototype.hasOwnProperty('toString'); // true
Function.prototype.hasOwnProperty('toString'); // true
var num = new Number('123sd');
num.toString(); // 'NaN'
var str = new String('12df');
str.toString(); // '12df'
var bool = new Boolean('fd');
bool.toString(); // 'true'
var arr = new Array(1,2);
arr.toString(); // '1,2'
var d = new Date();
d.toString(); // "Sun Jul 22 2018 12:38:42 GMT+0800 (中國標準時間)"
var func = function () {}
func.toString(); // "function () {}"
var obj = new Object({});
obj.toString(); // "[object Object]"
Math.toString(); // "[object Math]"

除這些對象及其實例化對象之外,其他對象返回的都是該對象的類型,都是繼承的Object.prototype.toString方法。

從上面valueOf和toString兩個函數(shù)對對象的轉換可以看出為什么對于ToPrimitive(input, PreferredType?)

PreferredType沒有設定的時候,除了Date類型,PreferredType被設置為String,其它的會設置成Number。

  • 因為valueOf函數(shù)會將Number、String、Boolean基礎類型的對象類型值轉換成 基礎類型,Date類型轉換為毫秒數(shù),其它的返回對象本身.
  • 而toString方法會將所有對象轉換為字符串。
  • 顯然對于大部分對象轉換,valueOf轉換更合理些,因為并沒有規(guī)定轉換類型,應該盡可能保持原有值,而不應該想toString方法一樣,一股腦將其轉換為字符串。

所以對于沒有指定PreferredType類型時,先進行valueOf方法轉換更好,故將PreferredType設置為Number類型。

而對于Date類型,其進行valueOf轉換為毫秒數(shù)的number類型。在進行隱式轉換時,沒有指定將其轉換為number類型時,將其轉換為那么大的number類型的值顯然沒有多大意義。(不管是在+運算符還是==運算符)還不如轉換為字符串格式的日期,所以默認Date類型會優(yōu)先進行toString轉換。故有以上的規(guī)則:

PreferredType沒有設置時,Date類型的對象,PreferredType默認設置為String,其他類型對象PreferredType默認設置為Number。

2.2、通過To Number將值轉換為數(shù)字

28.png

注意:

'12a'==12
false

2.3、通過ToString將值轉換為字符串

根據參數(shù)類型進行下面轉換:
undefined ’undefined’
null ’null’
布爾值轉換為’true’ 或 ‘false’
數(shù)字轉換字符串,比如:1.765轉為’1.765’
對象(obj)先進行 ToPrimitive(obj, String)轉換得到原始值,在進行ToString轉換為字符串
講了這么多,是不是還不是很清晰,先來看看一個例子:

({} + {}) 
//[object Object]

兩個對象的值進行+運算符,肯定要先進行隱式轉換為原始類型才能進行計算。
1、進行ToPrimitive轉換,由于沒有指定PreferredType類型,{}會使默認值為Number,進行ToPrimitive(input, Number)運算。
2、所以會執(zhí)行valueOf方法,({}).valueOf(),返回的還是{}對象,不是原始值。
3、繼續(xù)執(zhí)行toString方法,({}).toString(),返回"[object Object]",是原始值。
故得到最終的結果,"[object Object]" + "[object Object]" = "[object Object][object Object]"

再來一個指定類型的例子:

2 * {} 
 //NaN

1、首先*運算符只能對number類型進行運算,故第一步就是對{}進行ToNumber類型轉換。
2、由于{}是對象類型,故先進行原始類型轉換,ToPrimitive(input, Number)運算。
3、所以會執(zhí)行valueOf方法,({}).valueOf(),返回的還是{}對象,不是原始值。
4、繼續(xù)執(zhí)行toString方法,({}).toString(),返回"[object Object]",是原始值。
5、轉換為原始值后再進行ToNumber運算,"[object Object]"就轉換為NaN。
故最終的結果為 2 * NaN = NaN

3、== 運算符隱式轉換

類型相同時,沒有類型轉換,主要注意NaN不與任何值相等,包括它自己,即NaN !== NaN。

==在比較的時候可以轉換數(shù)據類型,若等式兩邊數(shù)據類型不相同,將會往數(shù)值類型方向轉換后再進行比較

1、x,y 為null、undefined兩者中一個 // 返回true

2、x、y為Number和String類型時,則轉換為Number類型比較。

3、有Boolean類型時,Boolean轉化為Number類型比較。

4、一個Object類型,一個String或Number類型,將Object類型進行原始轉換后,按上面流程進行原始值比較。


27.jpg
"" == 0  //ture
" " == 0  //ture
"" == true  //false
"" == false  //ture
" " == true  //false
" " == true  //false
" " == false  //ture
"hello" == true  //false
"hello" == false //false
"0" == true  //false
"0" == false  //true
"00" == false  //true
"0.00" == false  //true
undefined == null  //ture
 true =={} //false
[] == true  //false
var obj = { 
  a: 0, 
  valueOf: function(){return 1} 
} 
obj == "[object Object]"  //false
obj == 1  //true
obj == true  //true

注意

1.==等好兩邊都為對象時,比較的是地址

[]==[]
false
{}=={}
false

2.對象轉化成數(shù)值為NaN,數(shù)組則不一定。

let arr=[123]
undefined
let obj = {123:123}
undefined
Number(arr)
123
Number(obj)
NaN
obj == 123
false
arr == 123
true

1.png

3.1、== 例子解析

所以類型不相同時,可以會進行上面幾條的比較,比如:

var a = {
  valueOf: function () {
     return1;
  },
  toString: function () {
     return'123'
  }
}
true == a // true;

boolean類型進行ToNumber轉換為1
object類型,對y進行原始轉換,ToPrimitive(a, ?),沒有指定轉換類型,默認number類型。
而后,ToPrimitive(a, Number)首先調用valueOf方法,返回1,得到原始類型1。
最后 1 == 1, 返回true。

我們再看一段很復雜的比較,如下:

[] == !{}
true

1、! 運算符優(yōu)先級高于==,故先進行!運算。
2、!{}運算結果為false,結果變成 [] == false比較。
3、轉換數(shù)字類型結果變成 [] == 0。
比較變成ToPrimitive([]) == 0。
按照上面規(guī)則進行原始值轉換,[]會先調用valueOf函數(shù),返回this。
不是原始值,繼續(xù)調用toString方法, [].toString() = ''。
故結果為 '' == 0比較。
5、 ToNumber('') = 0。
所以結果變?yōu)椋?0 == 0,返回true,比較結束。

最后看看這道題目:

const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('hello world!');
}

1、當執(zhí)行a == 1 && a == 2 && a == 3 時,會從左到右一步一步解析,首先 a == 1,會進行上面第9步轉換。ToPrimitive(a, Number) == 1。

2、ToPrimitive(a, Number),按照上面原始類型轉換規(guī)則,會先調用valueOf方法,a的valueOf方法繼承自Object.prototype。返回a本身,而非原始類型,故會調用toString方法。

3、因為toString被重寫,所以會調用重寫的toString方法,故返回1,注意這里是i++,而不是++i,它會先返回i,在將i+1。故ToPrimitive(a, Number) = 1。也就是1 == 1,此時i = 1 + 1 = 2。

4、執(zhí)行完a == 1返回true,會執(zhí)行a == 2,同理,會調用ToPrimitive(a, Number),同上先調用valueOf方法,在調用toString方法,由于第一步,i = 2此時,ToPrimitive(a, Number) = 2, 也就是2 == 2, 此時i = 2 + 1。

5、同上可以推導 a == 3也返回true。故最終結果 a == 1 && a == 2 && a == 3返回true

再比如:

{}+[]
0
[]+{}
"[object Object]"
0+{}
"0[object Object]"
{}+0
0

是因為{}可以是空塊或空對象文字,具體取決于上下文。

  • 可以是一元加運算符,也可以是連接運算符,具體取決于上下文。

第一個代碼示例是一個空塊,它可能也不存在,使表達式相同+[],意思是“空數(shù)組轉換為數(shù)字”。

你不能把一個塊作為一個函數(shù)參數(shù),所以第二個代碼示例{}是一個對象,代碼的意思是“用一個數(shù)組來傳遞一個對象”(隱式地將對象和數(shù)組轉換為字符串)。換句話說,{} + []表達式是一個empty代碼塊,后面跟array一個數(shù)字(Number[])的約束。

在第二個示例中,您只是向concat數(shù)組提供了一個對象文字(空對象)。這就是你收到的原因[object Object]。
再比如:

null==0
false
null==1
false
+null
0

Null類型的比較不等于1或0(實際上,null僅與undefined 相當),但當強制轉換ToNumber(null)===0;

最終null==undefined//true ,實在無法理解,暫時只能理解這就是個坑,強行死記硬背。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容