JavaScript的類型轉換你真的懂嗎?

1. 值類型轉換

什麼是值類型轉換?數組

值類型轉換有兩種,一種是顯式類型轉換:就是將值從一種類型轉換爲另外一種類型,一般稱爲類型轉換。一種是隱式類型轉換,被稱爲強制類型轉換。安全

兩者的區別是顯而易見的,能夠從代碼中直接看出來那些類型轉換是顯式類型轉換,那些類型轉換是隱式類型轉換bash

var a = 12; 
var b = a + "";   //隱式強制類型轉換
var c = String(a);  //顯式強制類型轉換
複製代碼

對變量b來講,因爲+運算符的其中一個操做數是字符串,因此是字符串拼接操做,結果數字12被拼接爲了字符串"12",因此能夠說b的強制類型轉換是隱式的;函數

對於c來講,是經過String()將a顯式強制類型轉換爲了字符串,因此能夠說變量c的強制類型轉換是顯式的。工具

有人說隱式強制類型轉換的弊端大於顯式強制類型轉換,由於隱式強制類型轉換有時候會讓開發者摸不着頭腦。性能

可是顯式和隱式都是相對而言的,若是你明白字符串拼接,那麼a + "" 就是顯式的,若是你不瞭解String()能夠作字符串強制類型轉換,那麼它對於你來講就是隱式的。ui

有位名人說過,咱們編寫的代碼是給開發者看的,順便運行在了程序中。this

2. 抽象值操做

在討論顯式和隱式強制類型轉換以前,咱們須要先了解一下字符串、數字、布爾值之間類型轉換的規則es5

ToStringspa

ToString負責處理費字符串到字符串的強制類型轉換。

基本類型之的字符串化規則爲: null轉換爲"null",undefined轉換爲"undefined",true轉換爲"true"。

能夠查看ESMA規範中的9.8節:

對普通對象來講,除非是自行定義,不然tostring()返回內部屬性[[Class]]的值,如"[object Object]"

而數組比較例外,數組默認的toString()方法通過了從新定義,將全部單元字符串化之後再用",",連接起來,相似於join(",");

var arr = [1,2,3];
a.toString();  //"1,2,3"
arr.join(","); //"1,2,3"
複製代碼

toString()能夠被顯式調用,或者在須要字符串化時自動調用。

JSON字符串化

工具函數JSON.stringify()在將JSON對象序列化爲字符串時也用到了ToString()。對大多數簡單值來講,JSON字符串化和toString()的效果基本相同,不一樣的是JSON.stringify()序列化的結果老是字符串。 例如

JSON.stingify(42);  //"42"
SON.stingify("42");  //""42"" (對字符串序列化的時候,會生成一個含有雙引號的字符串)
SON.stingify(null);  //"null"
SON.stingify(false);  //"false"
複製代碼

可是JSON.stringify()並非強制類型轉換,談到它是由於它涉及到了ToString();

ToNumber

有時候咱們須要將非數字當作數字使用。

基本類型的數字轉換則爲: true轉換爲了1,false轉換爲了0,undefined轉換爲了NaN,null轉換爲0。

能夠查看ESMA規範中的9.3節:

對象(包括數組)會首先被轉換爲響應的基本類型之,若是返回的是非數字的基本類型值,則遵循上邊的規則將其強制轉換爲數字。

爲了將值轉換爲相應的基本類型值,會首先調用ToPrimitive檢查該值是否有valueOf()方法,若是有就返回基本類型值,使用該值進行強制類型轉換。若是沒有的話就使用toSting()的返回值進行強制類型轉換,若是兩者都不存在,則產生TyoeError錯誤。可是要注意的一點是在從ES5開始,使用Object.create(null)建立對象的[[Prototype]]的屬性爲null,因此是沒有valueOf()和toString()方法的,因此是沒有辦法進行強制類型轉換的。 例如

var a = {
    valueOf: function() {
        retrun "12"
    }
};
var b = {
    toString: function() {
        return: "12";
    }
}
var c = [1,2];
c.toString = function() {
    return this.join("")
}

Number(a);  //12
Number(b); //12
Number(c); //12
Number(""); //0
Number([]); //0
Number(["abcd"]); //NaN
複製代碼

ToBoolean

咱們知道在JavaScript中有兩個經常使用的關鍵詞true和false,表明了布爾類型的真值和假值。而在比較的時候咱們常誤覺得true和false等同於1和0,雖然咱們能夠將1強制轉換爲true,將0強制轉換爲false,反之亦可,可是在JavaSript中布爾值和數字是不同的。

參考ESMA規範

JavaScript中的值能夠分爲兩類

(1)能夠被強制類型轉換爲false的值

(2)能夠被強制類型轉換爲true的值(其餘) 由於能夠被強制類型轉換爲true的值太多了,可是能夠被轉換爲false的值缺只有一小部分,咱們只要記住哪些值是假值就能夠區分出真值和假值了。

如下這些是假值

null
undefined
false
+0、-0和NaN
""
複製代碼

從邏輯上理解,假值之外的都應該是真值。

3.顯式強制類型轉換

所謂的顯式強制類型轉換就是顯而易見的類型轉換。

var a = 12;
var b = String(a);
console.log(b)  //"12"

var c = "66.88"
var d = Number(c);
console.log(d);  //66.88
複製代碼

以上是字符串和數字之間的轉換,經過String()和Number()這兩個內建函數實現。String()遵循ToString()規則,Number()遵循ToNumber()規則,將值轉換爲基本類型。

除此以外還有其餘方法能夠實現數字和字符串之間的顯式轉換:

var a = 12;
var b = a.toString();
console.log(b);  //"12"

var c = "66.88";
var d = +c;
console.log(d); //66.88
複製代碼

a.toString()是顯式的,可是其中包含了隱式轉換,由於基本類型值12是沒有toString()方法的,因此須要先對數字12進行封裝,而後再調用toString()方法,而+c則是利用了+一元運算符能夠將c強制轉換爲數字。在項目中使用+一元運算符將日期顯式轉換爲數字時間戳也是很常見的一種使用方式。 例如:

var d = new Date("Sat Apr 27 2019 18:46:56 GMT+0800 (中國標準時間)")
+d;   //1556362016000
複製代碼

固然將日期顯式轉換爲時間戳還有其餘方式:

var d = new Date().getTime(); //1556362150329
var d1 = Date.now();  //1556362186672
複製代碼

固然這幾種方法中是有一些細微區別的,+d是將時間戳的毫秒都改成了000的數值,不是很精確,而new Date().getTime()能夠得到指定時間的時間戳,Date.now()能夠得到當前時間的時間戳,能夠根據須要快速的獲取所須要的時間戳。

在不少時候咱們須要將"100px"轉換爲數字100,這個時候咱們使用轉換就不行了,須要用到解析,雖然解析和轉換都是將數字字符串轉換爲數字,可是兩者是有區別的,轉換隻能轉換字符串中只包含數字的字符,可是解析能夠解析字符串中包含非數字字符,解析咱們能夠用parseInt()方法,例如:

var a = "100";
var b = "100px";
Number(a);  //100
parseInt(a);  //100
Number(b);  //NaN
parseInt(b); //100
複製代碼

parseInt()方法只能解析字符串值,傳遞其餘參數是無用的,例如數字,布爾值(true),對象([1,2,3])等等;

說完了字符串和數字之間的轉換,下面來看一下從非布爾值轉換爲布爾值。

var a = [];
var b = {};
var c = "0";
var d = 0;
var e = "";
var f = null;
var g = undefined;
var h;
Boolean(a);  //true
Boolean(b);  //true
Boolean(c);  //true
Boolean(d);  //false
Boolean(e);  //false
Boolean(f);  //false
Boolean(g);  //false
Boolean(h);  //false 
!!c //true
!!d //false
複製代碼

看一下是否是和咱們前邊說的是同樣的,少數假值以外,其餘的都是真值,雖然Boolean()是顯式轉換,可是在平常項目中並不經常使用, 更經常使用的是!符號,而在if() {}判斷語句中,若是沒有使用Boolean()和!符號,那麼會自動進行隱式轉換,這也是爲何在項目中能夠直接用if判斷是否符合條件的緣由。可是建議使用Boolean()和!!進行顯式轉換,這樣可讓代碼具備更好的可讀性。

4.隱式類型轉換

隱式類型轉換指的是那些隱蔽的強制類型轉換,隱式強制類型轉換可使代碼更爲簡潔,減小冗餘代碼。隱式類型轉換沒有明確的轉換方式,只要你本身以爲不是明顯的顯式強制類型轉換,那麼你均可以認爲它是隱式強制類型轉換。

字符串和數字二者之間的隱式類型強制轉換,例如:

var a = '100';
var b = '0';

var c = 100;
var d = 0;
a + b;   //'1000'
c + d;    //100
a + c;  //'100100'
b + d;   //'00'
複製代碼

再看對象(數組)之間的隱式類型轉換:

var a = [1,2];
var b = [3,4];
a + b; //'1,23,4'
複製代碼

從以上例子咱們能夠看出來,若是某個操做數是字符串的話,將會使用+進行拼接操做,若是二者都是數字的話,則會使用數字運算進行加法運算。而若是其中一個操做數是對象(數組)的話,則會對該操做數調用ToPrimitive抽象操做。

進行ToPrimitive抽象操做的時候,會調用valueOf()方法,若是調用valueOf()方法沒法獲得簡單基本類型值,就會轉而調用toString()方法,反之亦然。

布爾值到數字的隱式強制類型轉換

通常狀況下,咱們都是將其餘類型向布爾值轉換,可是有時候使用布爾值向數字轉換也會有使用場景。

例如:

var a = true;
var b = false;
a + b;  //1
a + a; //2
b + b; //0
複製代碼

經過以上例子能夠看出來,在轉換的時候true會轉換爲數字1,false會轉換爲數字0。這樣的話,在作一些多重條件判斷的時候就會用到了。例如,咱們想有且僅有一個條件爲true時達成條件。

var a = true;
var b = false;
function onlyOneTrue(a,b,c) {
    return !!((a && !b && !c) ||(!a && b && !c) );
}
onlyOneTrue(a, b, b) ; //true
onlyOneTrue(a, b, a);  false
複製代碼

能夠看到,在條件很少的時候,咱們還能夠寫出來,若是須要傳入5個,6個,N個參數的話,就很難處理了,在這樣的業務場景下就能夠將布爾值轉換爲數字。

var a = true;
var b = false;
function onlyOneTrue() {
    var sum = 0;
    for ( var i = 0; i < arguments.length; i++ ) {
        sum += Number(!!arguments[i])
    }
    return sum === 1;
}
onlyOneTrue(a,b,b,b,b,a,a,,b)
複製代碼

這樣的狀況下,不管咱們傳入多少參數,或者須要知足幾個ture,只須要修改sum的條件判斷就均可以了。

其餘類型值隱式強制轉換爲布爾值

其餘類型值隱式強制轉換爲布爾值,這種業務場景咱們就見的很是多了,例如:

1.if () 條件語句判斷表達式

2.for(.. ; ..; .. )語句中的條件判斷表達式

3.while()和do while()循環中的條件語句判斷表達式

4.? : 三元運算符條件判斷表達式

5.邏輯與(&&)運算符和邏輯或(||)左邊的操做數

寬鬆相等( == )和嚴格相等( === )

關於這兩種比較相等在社區中爭論已久,有的開發者認爲使用寬鬆相等( == )就能夠了,有的開發者認爲必須使用嚴格相等( === )。其實大可沒必要爭論到底使用哪一種方法是對的,只要咱們理解了這兩種相等的區別,就會恍然大悟。

二者區別: 寬鬆相等( == )容許在相等比較中進行強制類型轉換,而嚴格相等( === )是不容許在相等比較中進行強制類型轉換的。

雖然==比===作的事情更多,工做量大一些,在強制類型轉換的時候須要多花費一點時間,可是僅僅是微秒(百萬分之一秒)的差異而已,因此這一點時間並不能影響咱們代碼的性能。而在其餘方面二者之間並無什麼不一樣。因此咱們在寫項目中能夠放心的使用寬鬆相等( == ),而不是設置規範必須去使用嚴格相等( === )。

寬鬆不相等 != 就是==的相反方法,!==同理。

寬鬆相等

1.null 和 undefined兩值之間的相等比較

var a = null;
var b;

a == b; //true
b == null; //true

a == false; //false
b == fasle; //false
a == 0; //fasle
b == 0; //fasle
a == ''; //fasle
b == ''; //fasle
複製代碼

由以上例子能夠看出來在寬鬆比較( == )的時候,null和undefined是一回事的,能夠進行隱式強制類型轉換,可是除此以外其餘值都不和他們兩個相等。

因此在處理一些判斷的時候,能夠經過這種方式將null和undefined做爲等價的方式比較,例如:

if ( a == null ) {
    
}
或
if ( a == undefined ) {
    
}
等價於
if ( a == null || a == undefined ) {
    
}
複製代碼

這樣寫的話既保證了安全性,又能夠保證了代碼的簡潔性。

2.字符串和數字之間的比較

var a = 100;
var b = '100';
a === b; //false
a == b; //true
複製代碼

由於===不進行強制類型轉換,因此a !== b,即100和'100'不相等,而 == 進行了隱式類型轉換,因此100 == '100'。

字符串和數字之間寬鬆相等的比較規則,能夠根據EAMAScript規範11.9.3.4-5來解讀一下,即:

若是兩邊比較的值:一方值爲數字,一方值爲字符串,則對字符轉進行ToNumber()轉換。

3.布爾類型和其餘類型之間的相等比較

var a = '100';
var b = 100;
var c = true;
a == c; //false
b == c; //false
複製代碼

這是由於根據ESMA規範,若是兩邊比較的值,一方值爲true,一方值爲其餘類型,那麼會對布爾值進行ToNumber()轉換,先將布爾值轉換爲數字,再次進行比較。

4.對象和非對象之間的相等比較

var a = 100;
var b = [100];

a == b; //true
複製代碼

這是由於根據ESMA規範,若是兩邊比較的值,一方是字符串或者數字,一方是對象,那麼會使對象進行ToPrimitive()抽象操做,而後進行比較。例如上述例子,先將[42]經過ToPrimitive抽象操做返回了'100',變成了 '100' == 100,再根據字符串和數字之間比較的規則,將'100'轉換成了數字100,最後二者相等。

5.極端狀況

[] == ![]  //true
複製代碼

咱們知道全部的對象都強制類型轉換以後都爲true,上述例子看起來是真值和假值的比較,結果應該爲false,可是事實結果爲true,這是爲何呢?

咱們知道!會進行布爾值強制類型轉換,因此![]就被轉換爲了fasle,變成了[] == false, 而當布爾值和其餘類型進行比較的時候,會進行ToNumber(false), 因此![]就被轉換爲了0,變成了false == 0,那麼在布爾值和數字比較的時候,會進行ToNumber()轉換,因此最後[] == ![];

有不對的地方,歡迎指出,一塊兒討論。

參考:

1.你不知道的js中卷

2.ESMAScript5.1規範

相關文章
相關標籤/搜索