帶你擼一遍JS隱式轉換細則

寫在開篇之前:記錄學習點滴,如有錯誤與補充,希望大家積極指正。

js的數據類型

js數據類型:nullundefinedStringNumberBooleanSymbolObject,其中

原始類型:NumberStringNullUndefinedBoolean

引用類型:Object,包含Array

JavaScript 是一種弱類型或者說動態語言。這意味着你不用提前聲明變量的類型,在程序運行過程中,類型會被自動確定。這也意味着你可以使用同一個變量保存不同類型的數據。

常見的隱式轉換場景

我們經常在開發過程中遇到如下場景:

  • if判斷中
    if(XXX){
        ...
    }
複製代碼

這裏就發生了隱式轉換,我們知道if的判斷條件是Boolean類型,代碼在執行到if判斷時,js將XXX轉換成了Boolean類型。

  • 比較操作符 "=="
  [] == []  // false
  {} == {}  // false
  [] != []  // true
複製代碼

看到這幾個比較操作,如果比較懵的話,沒關係,後面我會娓娓道來。

  • 加號"+" 與 減號 "-"
var add = 1 + 2 + '3'
console.log(add);  //'33'

var minus = 3 - true
console.log(minus); //2

複製代碼

可以看出有些情況下+用作加號操作符,有些時候+作爲字符串連接符true被轉換爲Number類型,值爲1,相減後得到2。

  • .點號操作符
var a = 2;
console.log(a.toString()); // '2';

var b = 'zhang';
console.log(b.valueOf()); //'zhang';

複製代碼

在對數字,字符串進行點操作調用方法時,默認將數字,字符串轉成對象。

  • 關係運算符比較時
 3 > 4  // false
"2" > 10  // false
"2" > "10"  // true

複製代碼

如果比較運算符兩邊都是數字類型,則直接比較大小。如果是非數值進行比較時,則會將其轉換爲數字然後在比較,如果符號兩側的值都是字符串時,不會將其轉換爲數字進行比較,而是分別比較字符串中字符的Unicode編碼。

在討論之前我們需要先了解下 valueOf()toString 方法的一些要點。

  • toString()valueOf()都是對象的方法
  • toString()返回的是字符串,而valueOf()返回的是原對象
  • undefinednull沒有toString()valueOf()方法
  • 包裝對象的valueOf()方法返回該包裝對象對應的原始值
  • 使用toString()方法可以區分內置函數和自定義函數

大家沒事可以自己實踐下這兩個方法的使用和區別,其餘具體區別和特性不再詳述~

那麼接下來說說數據類型之間是如何轉換的吧。

String,Boolean,Number,對象之間的相互轉換

1. 其他類型轉爲字符串類型

  • null:轉爲"null"
  • undefined:轉爲"undefined"
  • Boolean:true轉爲"true"false轉爲"false"
  • Number:11轉爲"11",科學計數法11e20轉爲"1.1e+21"
  • 數組:空數組[]轉爲空字符串"",如果數組中的元素有null或者undefined,同樣當做空字符串處理,[1,2,3,4]轉爲"1,2,3,4",相當於調用數組的join方法,將各元素用逗號","拼接起來。
  • 函數:function a(){}轉爲字符串是"function a(){}"
  • 一般對象:相當於調用對象的toString()方法,返回的是"[object,object]"

    String(null)  // "null"
    String(undefined) // "undefined"
    String(true)  // "true"
    String(false)  // "false"
    String(11)  // "11"
    String(11e20)  // "1.1e+21"
    String([])  // ""
    String([1,null,2,undefined,3])  // 1,,2,,3
    String(function a(){})  // "function a(){}"
    String({})  // "[object,object]"
    String({name:'zhang'})  // "[object,object]"

複製代碼

2. 其他類型轉爲Boolean類型

只有nullundefined0falseNaN空字符串這6種情況轉爲布爾值結果爲false,其餘全部爲true,例子如下


    Boolean(null)  // false
    Boolean(undefined)  // false
    Boolean(0)  // false
    Boolean(false)  // false
    Boolean("false")  // true
    Boolean(NaN)  // false
    Boolean("")  // false
    Boolean([])  // true
    Boolean({})  // true

複製代碼

3. 其他類型轉爲Number類型

  • null:轉爲 0
  • undefined:轉爲NaN
  • Boolean:true轉爲1false轉爲0
  • 字符串:如果是純數字的字符串,則轉爲對應的數字,如11轉爲"11""1.1e+21"轉爲1.1e+21空字符串轉爲0,其餘情況則爲NaN
  • 數組:數組首先會被轉換成原始類型,即primitive value,得到原始類型後再根據上面的轉換規則轉換。
  • 對象:和數組一樣
Number(null)  // 0
Number(undefined)  //NaN
Number(true)  //1
Number(false)  //0
Number("11")  //11
Number("1.1e+21") //1.1e+21
Number("abc")  //NaN
Number([])   // 0
Number([0])  // 0
Number([1])  // 1
Number(["abc"])  NaN
Number({})  // NaN

複製代碼

4. 對象轉爲其他類型(原始類型)

  • 當對象轉爲其他原始類型時,會先調用對象的valueOf()方法,如果valueOf()方法返回的是原始類型,則直接返回這個原始類型
  • 如果valueOf()方法返回的是不是原始類型或者valueOf()方法不存在,則繼續調用對象的toString()方法,如果toString()方法返回的是原始類型,則直接返回這個原始類型,如果不是原始類型,則直接報錯拋出異常。

注意:對於不同類型的對象來說,轉爲原始類型的規則有所不同,比如Date對象會先調用toString

    var o1 = {
        valueOf(){
            return 1
        }
    }
    var o2 = {
        toString(){
            return 2
        }
    }
    var o3 = {
        valueOf(){
            return {}
        },
        toString(){
            return 3
        }
    }

    Number(o1)  //1
    Number(o2)  //2
    Number(o3)  //3

複製代碼

o1valueOf方法返回原始類型1,故結果爲1

o2沒有valueOf方法,調用toString方法得到原始類型2,故結果爲2

o3既有valueOf方法,也有toString方法,先調用valueOf方法返回的不是原始類型,故繼續調用toString方法,返回的是原始類型3,故結果爲3

我們再看一個例子

將一個空數組轉成Number類型,空數組是一個對象,先調用valueOf()得到[],不是原始類型,繼續調用toString()方法,得到的是一個空字符串,根據Number的轉換規則,空字符串轉換成數字0,故Number([])0

寬鬆相等(==)的隱式轉換

我們知道寬鬆相等是存在隱式轉換的,相比較時只要兩邊的值相等,不需要類型相等,就可以得到true,那麼看看有哪些情況的比較吧

原始類型之間相比較

1. 字符串類型與數字類型相比較

  • 當字符串類型與數字類型相比較時,字符串類型會被轉換爲數字類型
  • 當字符串是由純數字組成的字符串時,轉換成對應的數字,字符串爲空時轉換爲0,其餘的都轉換爲NaN。 小例子可如下:

    "1" == 1  //true
    "" == 0  //true
    "1.1e+21" == 1.1e+21  //true
    "Infinity" == Infinity  //true
    NaN == NaN  //false  因爲NaN與任何值都不相等,包括自己

複製代碼

2. 布爾類型與其他類型相比較

  • 只要布爾類型參與比較,該布爾類型就會率先被轉成數字類型
  • 布爾類型true轉爲1false轉爲``0

小例子可如下:

true == 1  // true
false == 0  // true
true == 2  // false
"" == false // true
"1" == true // true
複製代碼

根據規則,布爾型參與比較,會把布爾類型轉爲數字類型。

  • 第一個demo,true被轉爲數字類型1,比較變爲1 == 1,結果爲true
  • 第二個demo,false被轉爲數字類型0,比較變爲0 == 0,結果爲true
  • 第三個demo,true被轉爲數字類型1,,比較變爲1 == 2,結果爲false
  • 第四個demo,false被轉爲數字類型0,比較變爲"" == 0,根據字符串與數字相比較,會率先把字符串變成數字,空字符串轉爲數字類型爲0,比較變爲0 == 0,故結果爲true
  • 第五個demo與第四個相似,true被轉換成數字類型1,比較變爲"1" == 1,根據字符串與數字相比較,會率先把字符串變成數字,字符串"1"轉爲數字類型爲1,變成1 == 1,故結果爲true

null類型和 undefined類型與其他類型相比較

首先得知道nullundefined的定義

null: 代表“空值”,代表一個空對象指針,使用typeof運算得到 "object",所以可以認爲它是一個特殊的對象值。

undefined:聲明瞭一個變量但並未爲該變量賦值,此變量的值默認爲undefined。

這裏多囉嗦一部分內容,nullundefined分別應用的場景:

null的場景:
  • 作爲原型鏈的終點
  • 作爲函數的參數,表示該函數的參數不是對象
undefined的場景:
  • 聲明瞭一個變量但並未爲該變量賦值,此變量的值默認爲undefined
  • 函數沒有明確寫return,默認返回undefined
  • 調用函數時,沒有傳參數,默認參數值爲undefined
  • 對象的某個屬性沒有賦值,默認值爲undefined

Javascript規定nullundefined寬鬆相等(==),並且都與自身相等,但是與其他所有值都不寬鬆相等。

null == null   //true
undefined == undefined  //true
null == undefined  //true
null == 0  //false
null == false  //false
undefined == 0  //false
undefined == false  //false
null == []  //false
null == {}  //false
unfefined == []  //false
undefined == {}  //false
複製代碼

對象與原始類型的相比較

對象原始類型相比較時,會把對象按照對象轉換規則轉換成原始類型,再比較。

小例子如下:


 {} == 0  // false
 {} == '[object object]'  // true
 [] == false  // true
 [1,2,3] == '1,2,3' // true

複製代碼

先看一下,根據對象轉換規則,{},[],[1,2,3]轉爲原始類型後的結果如下:

第一個例子,根據上圖可知,{}的原始值爲"[object object]",比較變成"[object object]" == 0,接着根據字符串與數字類型相比較規則,先將字符串轉換成數字類型,可知[object object]轉爲數字爲NaN,比較變成NaN == 0,因爲NaN與任何值都不想等,故結果爲false

第二個例子,由第一個分析可知,{}的原始值爲"[object object]",比較變成"[object object]" =="[object object]",故結果爲true

第三個例子,根據上圖可知,[]的原始值爲空字符串"",比較變成"" == 0,接着根據字符串與數字類型相比較規則,先將字符串轉換成數字類型,可知""轉爲數字爲0,比較變成0 == 0,故結果爲true

第四個例子,根據數組轉換成原始類型的規則可知,數組的原始類型結果是由數組各個元素由逗號進行分割組成的字符串,故比較變成"1,2,3" == "1,2,3",所以結果爲true

對象與對象相比較

首先先說一下基本類型(原始類型)與引用類型的存儲。

基本類型:是指存放在棧內存中的簡單數據段,數據大小確定,內存空間大小可以分配,它們是直接按值存放的,所以可以直接按值訪問。

引用類型:是指存放在堆內存中的對象,變量名保存在棧內存裏,而對應的值保存在堆內存裏,這個變量在棧內存中實際保存的是:這個值在堆內存中的地址,也就是指針,指針指向堆內存中的地址。

那麼對象相比較的規則就出來了:

如果兩個對象指向同一個對象,相等操作符返回 true,否則爲false

var a = {};
var b = {};
a == b  // false

var c = [];
var d = [];
c == d  // false
複製代碼

雖然 a 和 b 都保存了一個 Object,但這是兩個獨立的 Object,它們的地址是不同的。c與d也是如此。 所以 [] == []false{} == {} 也是false。 那麼改成如下呢?

var a = {};
var b = a;
a == b;
複製代碼

變量b保存的是a的指針,指向同一個對象,所以a == b。

那麼接下來看下面的例子

[] == ![]  // true
{} == !{}  // false
複製代碼

Javascript規定,邏輯非 (!) 的優先級高於相等操作符 ( == )

再則取非的含義時什麼呢?

取非:首先通過Boolean()函數將它的操作值轉換爲布爾值,然後求反

第一個例子,先看![],也就是對空數組[]取非,根據取非定義,先執行Boolean([]),我們知道只有nullundefinedfalse0""NaN,執行Boolean()函數時結果才爲false,取餘全爲true,故Boolean([])結果爲true,取非得到false,比較變爲[] == false, 這下變成了對象類型布爾類型相比較了,這就比較簡單了,根據前面的對象類型比較規則,布爾類型比較規則,很容易的出比較變爲"" == 0,再根據字符串與數字類型相比較規則,比較變爲0 == 0,顯然結果爲true

第二個例子,同第一個例子一樣,先執行Boolean({})得到true,再取反得到false,比較變爲{} == false,現在比較變爲對象類型布爾類型相比較了,根據之前寫的例子知道,先指向{}的valueOf()方法得到的不是原始類型,繼續執行{}的toString()方法到的結果爲"[object object]",比較變爲"[object object]" == 0,再根據字符串與數字類型相比較規則,"[object object]"轉爲數字類型爲NaN,比較變成NaN == 0,NaN不與任何值相等,顯然結果爲false

之前比較火的一個面試題:怎麼定義a,可以使a==1&&a==2&&a==3?結合對象隱式轉換規則,試一試吧!

字符串連接符(+)與加號運算符(+)如何區分

以下情況可視爲字符串連接符

  • 含有+兩邊的數據,任意一個爲字符串
  • 含有+兩邊的數據,其中一邊爲對象,並且取得的原始值爲字符串

以上2種情況都視爲字符串連接符,將自動的對不是字符串的數據執行String()方法轉爲字符串

以下情況可視爲加號運算符

  • 加號兩邊都爲數字類型
  • 加號兩邊都是基本類型,除了字符串類型,則可視爲加號。也就是說加號兩邊可以爲BooleanNumbernullundefined這種情況,肯定是加號運算符。將對不是Numebr類型的數據執行Number()方法再相加。
  • 其中一邊是基本類型,字符串除外,另一邊是對象,並且對象獲得的原始值不是字符串。

另外值得一提的是: NaN與任何數相加都爲NaN,

那麼接下來看下面的例子

"a" + "b"  // "ab"
"a" + 1    // "a1"
"a" + {}   // "a[object object]"
"a" + []   // "a"
"a" + true  // "atrue"
"a" + null  // "anull"
"a" + undefined // "aundefined"

 1 + 2     // 3
 1 + true  // 2
 1 + null  // 1
 true + true   // 2
 true + false  // 1
 true + null   // 1
 false + null  // 0

 var c = {
     valueOf(){
         return 1;
     }
 }

 1 + undefined   // NaN
 true + undefined  // NaN
 true + c   // 2
 1 + c   // 2

 NaN + 1  // NaN
 NaN + null  // NaN

 [] + {}  // "[object object]"
 {} + []  // 0
複製代碼

這裏值得提出來的是Number(undefined)NaN,不難看出上面幾個的結果爲啥等於NaN。 那麼[] + {}{} + []只是調了一個位置,爲什麼結果會大不相同呢?

我們知道[]的原始值爲""{}的原始值爲[object object],在這裏就不再重述了。 那麼第一個例子,拼接起來等於"[object object]"沒什麼問題,那麼第二個例子是爲什麼呢?

原來是js解釋器會將開頭的 {} 看作一個代碼塊,而不是一個js對象,於是運算可寫成+[],這結果可不就是0嘛,上代碼看一下

[圖片上傳失敗...(image-bbb4d8-1623944329379)]

<figcaption></figcaption>

大家可以自己自己操作看看~~~

好了,大致就是這麼多了,也是看了很多文章和結合高程寫出來的,擼了一遍,着實加深了印象,給自己點個贊,嘻嘻嘻~~~

作者:Autumn秋田
鏈接:https://juejin.cn/post/6844903934876745735
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章