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ù)字

注意:
'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類型進行原始轉換后,按上面流程進行原始值比較。

"" == 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

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;