深刻js隱式類型轉換

前言

相信剛開始瞭解js的時候,都會遇到 2 =='2',但 1+2 == 1+'2'爲false的狀況。這時候應該會是一臉懵逼的狀態,不得不感慨js弱類型的靈活讓人髮指,隱式類型轉換就是這麼猝不及防。結合實際中的狀況來看,有意或無心中涉及到隱式類型轉換的狀況仍是不少的。既然要用到,就須要掌握其原理,知其然重要知其因此然更重要。git

js的變量類型

JavaScript 是弱類型語言,意味着JavaScript 變量沒有預先肯定的類型。
而且變量的類型是其值的類型。也就是說變量當前的類型由其值所決定,誇張點說上一秒種的string,下一秒可能就是個array了。此外當進行某些操做時,變量能夠進行類型轉換,咱們主動進行的就是顯式類型轉換,另外一種就是隱式類型轉換了。例如:es6

var a = '1';   
typeof a;//string 

a =parseInt(a); //顯示轉換爲number
typeof a  //number   

a == '1' //true

弱類型的特性在給咱們帶來便利的同時,也會給咱們帶來困擾。趨利避害,充分利用該特性的前提就是掌握類型轉換的原理,下面一塊兒看一下。github

js數據類型

老生常談的兩大類數據類型:算法

  1. 原始類型
    Undefined、 Null、 String、 Number、 Boolean
  2. 引用類型
    object

此外還有一個es6新增的Symbol,先不討論它。對於這五類原始類型,忽然提問可能想不全,不必去死記硬背,能夠想一下爲否的常見變量及其對應值便可。學習

0 Number
'' String
false Boolean
null Null
undefined Undefined

對於不一樣的數據格式轉換規則是不一樣的,咱們須要分別對待。this

轉換規則

既然是規範定義的規則,那就不要問爲何了,先大體看一下,爭取記住。是在不行常常翻翻看看大佬的博客es5規範。轉換有下面這麼幾類,咱們分別看一下具體規範。(這部分轉換規則,徹底能夠跳過去,看到下面的實例再回頭看應該更容易接受一些)es5

  • 轉換爲原始值
  • 轉換爲數字
  • 轉換爲字符串

ToPrimitive(轉換爲原始值)

ToPrimitive 運算符接受一個值,和一個可選的 指望類型 做參數。ToPrimitive 運算符把其值參數轉換爲非對象類型。若是對象有能力被轉換爲不止一種原語類型,可使用可選的 指望類型 來暗示那個類型。根據下表完成轉換 spa

1.png

這段定義看起來有點枯燥。轉換爲原始值,其實就是針對引用數據的,其目的是轉換爲非對象類型。
若是已是原始類型,固然就不作處理了
對於object,返回對應的原始類型,該原始類型是由指望類型決定的,指望類型其實就是咱們傳遞的type。直接看下面比較清楚。
ToPrimitive方法大概長這麼個樣子具體以下。3d

/**
* @obj 須要轉換的對象
* @type 指望轉換爲的原始數據類型,可選
*/
ToPrimitive(obj,type)

type能夠爲number或者string,二者的執行順序有一些差異
string:code

  1. 調用obj的toString方法,若是爲原始值,則返回,不然下一步
  2. 調用obj的valueOf方法,後續同上
  3. 拋出TypeError 異常

number:

  1. 調用obj的valueOf方法,若是爲原始值,則返回,不然下一步
  2. 調用obj的toString方法,後續同上
  3. 拋出TypeError 異常

其實就是調用方法前後,畢竟指望數據類型不一樣,若是是string固然優先調用toString。反之亦然。
固然type參數能夠爲空,這時候type的默認值會按照下面的規則設置

  1. 該對象爲Date,則type被設置爲String
  2. 不然,type被設置爲Number

對於Date數據類型,咱們更多指望得到的是其轉爲時間後的字符串,而非毫秒值,若是爲number,則會取到對應的毫秒值,顯然字符串使用更多。
其餘類型對象按照取值的類型操做便可。

歸納而言,ToPrimitive轉成何種原始類型,取決於type,type參數可選,若指定,則按照指定類型轉換,若不指定,默認根據實用狀況分兩種狀況,Date爲string,其他對象爲number。那麼何時會指定type類型呢,那就要看下面兩種轉換方式了。

toNumber

某些特定狀況下須要用到ToNumber方法來轉成number
運算符根據下表將其參數轉換爲數值類型的值
2.png

對於string類型,狀況比較多,只要掌握常見的就好了。和直接調用Number(str)的結果一致,這裏就很少提了,主要是太多提不完。
須要注意的是,這裏調用ToPrimitive的時候,type就指定爲number了。下面的toString則爲string。

toString

ToString 運算符根據下表將其參數轉換爲字符串類型的值:
其實瞭解也很簡單,畢竟是個規範,借用大佬一張圖:

3.png

雖然是須要死記的東西,仍是有些規律可循的。
對於原始值:

  • Undefined,null,boolean
    直接加上引號,例如'null'
  • number 則有比較長的規範,畢竟範圍比較大
    常見的就是 '1' NaN則爲'NaN' 基本等同於上面一條
    對於負數,則返回-+字符串 例如 '-2' 其餘的先不考慮了。
  • 對象則是先轉爲原始值,再按照上面的步驟進行處理。

valueOf

當調用 valueOf 方法,採用以下步驟:

  1. 調用ToObject方法獲得一個對象O
  2. 原始數據類型轉換爲對應的內置對象, 引用類型則不變
  3. 調用該對象(O)內置valueOf方法.

不一樣內置對象的valueOf實現:

  • String => 返回字符串值
  • Number => 返回數字值
  • Date => 返回一個數字,即時間值,字符串中內容是依賴於具體實現的
  • Boolean => 返回Boolean的this值
  • Object => 返回this

對照代碼更清晰一點

var str = new String('123')
//123
console.log(str.valueOf())
var num = new Number(123)
//123
console.log(num.valueOf())
var date = new Date()
//1526990889729
console.log(date.valueOf())
var bool = new Boolean('123')
//true
console.log(bool.valueOf())
var obj = new Object({valueOf:()=>{
    return 1
}})
//依賴於內部實現
console.log(obj.valueOf())

運算隱式轉換

前面提了那麼多抽象概念,就是爲了這裏來理解具體轉換的。
對於+運算來講,規則以下:

  • +號左右分別進行取值,進行ToPrimitive()操做
  • 分別獲取左右轉換以後的值,若是存在String,則對其進行ToString處理後進行拼接操做。
  • 其餘的都進行ToNumber處理
  • 在轉換時ToPrimitive,除去Date爲string外都按照ToPrimitive type爲Number進行處理

說的本身都迷糊了快,一塊兒結合代碼來看一下

1+'2'+false
  1. 左邊取原始值,依舊是Number
  2. 中間爲String,則都進行toString操做
  3. 左邊轉換按照toString的規則,返回'1'
  4. 獲得結果temp值'12'
  5. 右邊布爾值和temp一樣進行1步驟
  6. temp爲string,則布爾值也轉爲string'false'
  7. 拼接二者 獲得最後結果 '12false'

咱們看一個複雜的

var obj1 = {
    valueOf:function(){
        return 1
    }
}
var obj2 = {
    toString:function(){
        return 'a'
    }
}
//2
console.log(1+obj1)
//1a
console.log('1'+ obj2)
//1a
console.log(obj1+obj2)

無論多複雜,按照上面的順序來吧。

  • 1+obj1

    1. 左邊就不說了,number
    2. 右邊obj轉爲基礎類型,按照type爲number進行
    3. 先調用valueOf() 獲得結果爲1
    4. 兩遍都是number,則進行相加獲得2
  • 1+obj2

    1. 左邊爲number
    2. 右邊一樣按照按照type爲number進行轉化
    3. 調用obj2.valueOf()獲得的不是原始值
    4. 調用toString() return 'a'
    5. 依據第二條規則,存在string,則都轉換爲string進行拼接
    6. 獲得結果1a
  • obj1+obj2

    1. 兩邊都是引用,進行轉換 ToPrimitive 默認type爲number
    2. obj1.valueOf()爲1 直接返回
    3. obj2.valueOf()獲得的不是原始值
    4. 調用toString() return 'a'
    5. 依據第二條規則,存在string,則都轉換爲string進行拼接
    6. 獲得結果1a

到這裏相信你們對+這種運算的類型轉換了解的差很少了。下面就看一下另外一種隱式類型轉換

== 抽象相等比較

這種比較分爲兩大類,

  • 類型相同
  • 類型不一樣

相同的就不說了,隱式轉換髮生在不一樣類型之間。規律比較複雜,規範比較長,這裏也不列舉了,你們能夠查看抽象相等算法。簡單總結一句,相等比較就不想+運算那樣string優先了,是以number優先級爲最高。歸納而言就是,都儘可能轉成number來進行處理,這樣也能夠理解,畢竟比較仍是指望比較數值。那麼規則大概以下:
對於x == y

  1. 若是x,y均爲number,直接比較

    沒什麼可解釋的了
     1 == 2 //false
  2. 若是存在對象,ToPrimitive() type爲number進行轉換,再進行後面比較

    var obj1 = {
        valueOf:function(){
            return '1'
        }
    }
    1 == obj2  //true
    //obj1轉爲原始值,調用obj1.valueOf()
    //返回原始值'1'
    //'1'toNumber獲得 1 而後比較 1 == 1
    [] == ![] //true
    //[]做爲對象ToPrimitive獲得 ''  
    //![]做爲boolean轉換獲得0 
    //'' == 0 
    //轉換爲 0==0 //true
  3. 存在boolean,按照ToNumber將boolean轉換爲1或者0,再進行後面比較

    //boolean 先轉成number,按照上面的規則獲得1  
    //3 == 1 false
    //0 == 0 true
    3 == true // false
    '0' == false //true
  4. 若是x爲string,y爲number,x轉成number進行比較

    //'0' toNumber()獲得 0  
    //0 == 0 true
    '0' == 0 //true

結束語

參考文章

ECMAScript5.1中文版 + ECMAScript3 + ECMAScript(合集)
你所忽略的js隱式轉換
這篇文章的本意是爲本身解惑,寫到後面真的感受比較乏味,畢竟規範性的東西多一點,不過深刻了解一下總好過死記硬背。原文請移步個人博客。對於有些觀點說這些屬於js糟粕,徹底不該該深刻,怎麼說呢,結合本身狀況判斷吧。本人水平有限,拋磚引玉共同窗習。

相關文章
相關標籤/搜索