深刻理解 JavaScript 的類型轉換

前言

JavaScript做爲一門弱類型語言,咱們在天天的編寫代碼過程當中,無時無刻不在應用着值類型轉換,可是不少時候咱們只是在單純的寫,並未曾停下腳步去探尋過值類型轉換的內部轉換規則,最近經過閱讀你不知道的JavaScript中篇,對js的值類型轉換進行了更加深刻的學習,在此分享給你們參考學習。前端

概念

將值從一種類型轉換爲另外一種類型一般稱爲類型轉換,主要發生在靜態語言的編譯階段;強制類型轉換則發生在動態語言的運行階段;JavaScript做爲一門典型的動態弱類型語言天然而然採用的是強制類型轉換(即隱式強制類型轉換和顯式強制類型轉換);在js的強制類型轉換老是返回標量基本類型值,如字符串、布爾、數字,不會返回對象和函數git

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

前情提要

在閱讀後面的內容以前,咱們首先要明白下面幾個概念,以方便對後續內容的理解es6

  • 封裝對象 :eg:var a = new String('abc'),a被叫作封裝了基本類型的封裝對象,還原一個封裝對象的值,能夠調用valueOf方法;

基本類型的幾乎全部方法並不是來自自己,而是來自於封裝對象的原型對象,例以下面例子github

const a = 1.2;
console.log(a.toFixed(0));//1
複製代碼

基本類型數字並不存在toFixed方法,只是在訪問該方法時候,js自動封裝基本類型爲對應的封裝對象,再去訪問該封裝對象的原型上對應的方法,等同於下面例子正則表達式

const a = 1.2;
console.log(new Number(a).__proto__.toFixed());//0
複製代碼
  • ToPrimitive抽象操做:該操做主要是將對象類型轉換爲基本類型,首先檢查某個對象是否有valueOf屬性,若是有則返回該對象的valueOf的值,不然調用該對象的toString屬性並返回值(若是valueOf返回的不是基本類型則調用toString方法,例如數組的valueOf返回的仍是數組,全部ToPrimitive會默認調用toString方法);

抽象值操做

ToString負責處理非字符串到字符串的強制類型轉換,規則以下:

  • 1.null轉換爲'null',undefined轉換爲'undefined',其餘基本類型都調用基本類型的包裝對象屬性toString()並返回值。
const a = 123;
const _a = new Number(123);
console.log(String(a), _a.toString());//'123' '123'
const b = true;
const _b = new Boolean(true);
console.log(String(b), _b.toString());//'true' 'true'
複製代碼
  • 2.數字的字符串化遵循通用規則,可是極小極大數字使用指數形式
const a = 1.07*1000*1000*1000*1000*1000*1000*1000;
console.log(String(a));//'1.07e+21'
複製代碼
  • 3.對於普通對象來講,除非自行定義,不然toString()返回Object.prototype.toString()的值,其餘對象有本身的toString()方法則調用本身的該方法
const b = {};
console.log(String(b));//[object object]
複製代碼
  • 4.數組的默認toString()方法進行了從新定義,將全部單元字符串化之後再用‘,’鏈接起來
const a = [1, 2, 3];
console.log(String(a));//'1,2,3'
複製代碼
  • 5.JSON字符串化數組

    • 5-1.JSON字符串化和toString的效果基本相同,只不過序列化的結果老是字符串安全

    • 5-2.JSON對於不安全的值(undefined,function(){},symbol)直接忽略,數組中則以null填充bash

    • 5-3.對象循環引用直接報錯,正則表達式序列化爲{}函數

ToNumber負責處理非數字到數字的強制類型轉換,規則以下:

  • 1.true轉換爲1,false轉換爲0,undefined轉換爲NaN,null轉換爲0
console.log(Number(null));//0
console.log(Number(undefined));//NaN
console.log(Number(true));//1
console.log(Number(false));//0
複製代碼
  • 2.對字符串的處理遵循數字常量的相關規定/語法,處理失敗時返回NaN
console.log(Number('123'));//123
console.log(Number('0b111'));//7
console.log(Number('0o123'));//83
console.log(Number('0x123'));//291
console.log(Number('123a'));//NaN
console.log(Number('a123'));//NaN
複製代碼
  • 3.對象(包括數組)會首先按照ToPrimitive抽象操做被轉換爲相應的基本類型值,再按照前兩條規則處理;若是某個對象即不存在valueOf方法也不存在toString方法,則會產生TypeError錯誤(例如Object.create(null)不存在以上兩種方法)
const arr = [1, 2, 3];
console.log(Number(arr));//NaN
console.log(Number(arr.toString()));//NaN


const num = new Number(123);
console.log(Number(num));//123
console.log(Number(num.valueOf()));//123

const bool = new Boolean(true);
console.log(bool.valueOf());//true
console.log(Number(bool));//1
console.log(Number(bool.valueOf()));//1

const obj1 = {
  toString:()=>"21"
}

const obj2 = {
  valueOf:()=>"42",
  toString:()=>"21"
}

const obj3 = {
  a:1
}
console.log(Number(obj1));//21
console.log(Number(obj2));//42
console.log(obj3.toString());//[object Object]
console.log(Number(obj3));//NaN


const obj = Object.create(null);
console.log(Number(obj));//TypeError
複製代碼

上述obj1,obj2分別調用toString和valueOf方法,obj3調用原型上的toString()方法學習

ToBoolean負責處理非布爾值到布爾值的強制類型轉換,規則以下:

  • 能夠被轉換爲false的值(undefined,null,false, +0、-0和NaN,'')

  • 除條件1的其餘都被轉換爲true(切記:封裝對象均被轉爲true)

顯式強制類型轉換

字符串和數字之間的顯式轉換

  • 經過window下面的內建函數String()和Number()來實現(遵循上述抽象值操做)

  • toString()的顯式轉換過程爲先隱式的將基本類型轉爲封裝對象,再對該對象調用toString方法

  • 一元運算符+和-來轉換,例如+a顯式的將c轉換爲數字

  • 位運算NOT的顯式轉換

    • 第一步:位運算NOT將非數字和數字轉換爲32位數字
    • 第二步:將該數字求負值並減1
    • indexOf優雅寫法(返回-1的這種現象稱爲抽象滲漏,即代碼暴露了底層的實現細節)
    • 截除數字值得小數部分比Math.floor()更加靠譜(Math.floor對負數的截取和~不一樣)
const a = true;
console.log(~a === -Number(a)-1)//true

//indexOf優雅寫法
const b = 'abc';
if(~b.indexOf('d')){
  console.log('存在d')
}else{
  console.log('不存在d')
}

//數字的小數部分截除
const d = 3.14;
const e = -3.14;
console.log(Math.floor(d), ~~d);//3 3
console.log(Math.floor(e), ~~e);//-4 -3
複製代碼

顯式解析數字字符串

解析字符串中的數字和強制將字符串轉換爲數字返回的結果都是數字;可是解析容許字符串中含有非數字,解析按從左到右的順序,若是遇到非數字就中止解析;而轉換不容許出現非數字字符,不然會失敗並返回NaN。

  • parseInt()和parseFloat分別用來解析整數和浮點數,傳入的值必須是字符串,若是是非字符串會被隱式轉換爲字符串再解析

注意點:es5以前parseInt()遇到0開頭的字符串數字(好比時間hour='09')會被按照八進制處理,須要在第二個參數傳入10解決,es5以後0開頭的能能字符串化爲八進制的按八進制解析不能的按10進制解析;

//現將a轉爲字符串'1,2,3'
const a = [1, 2, 3];
console.log(parseInt(a));//1
console.log(parseFloat(a));//1

//現將true轉爲字符串'true'
console.log(parseInt(true));//NaN
console.log(parseFloat(true));//NaN

//現將3.14轉爲字符串'3.14'
console.log(parseInt(3.14));//3
console.log(parseFloat(3.14));//3.14

console.log(String(new Date()));//'Wed Jun 12 2019 21:23:59 GMT+0800'
console.log(parseInt(new Date()));//NaN
console.log(parseFloat(new Date()));//NaN

//關於es6以前八進制寫法的解析
console.log(parseInt(09));//9
console.log(parseFloat(09));//9
console.log(parseInt(010));//8
console.log(parseFloat(010));//8
複製代碼
  • parseInt()的一些奇怪現象
parseInt(1/0, 19);//18
//其實至關於parseInt(Infinity, 19);其中Infinity的I在十九進制數中爲18

parseInt(0.000008);//0
//字符串化爲0.00008後進行解析

parseInt(0.0000008);//8
//字符化爲8e-7後進行解析(詳見抽象ToNumber)

parseInt(0x10);//16
String(0x10);//16
parseInt(0b10);//2
String(0b10);//2
parseInt(0o10);//8
String(0o10);//8
parseInt(012);//10
String(012);//10
//其實如今es6規定了二進制、八進制和十六進制的表示法
//以上三個字符串均先被String()化爲字符串再進行解析
複製代碼

顯式轉換爲布爾值

  • 經過全局方法Boolean()強制轉換,遵循抽象值操做中的ToBolean

  • !!進行強制轉換,遵循抽象值操做中的ToBolean

隱式強制類型轉換

隱式強制類型轉換爲字符串

  • 一元運算符加號(+)首先把非基本類型經過ToPrimitive抽象操做轉換爲基本類型,若是加號中的兩項有一項是字符串,另外一項則進行ToString操做,進行字符串拼接,若是是布爾值加數字,則對布爾進行ToNumber操做再求和
const a = 1;
console.log(a + true);//2
//等同於
console.log(a + Number(true));//2

console.log([1, 2] + [1, 2]); //1,21,2
//等同於
console.log([1, 2].toString() + [1, 2].toString()); //1,21,2


console.log({} + []);//[object Object]
console.log([] + {});//[object Object]
console.log({}.toString());//[object Object]
console.log([].toString());//''
複製代碼
  • 隱式強制類型轉換爲數字,經過一元運算符-、/、*轉換,遵循ToNumber的抽象值操做規則
console.log('3.14' - '0');//3.14
console.log([2] - [1]);//1
//等同於
console.log([2].toString() - [1].toString());//1
複製代碼

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

如下均遵循ToBolean抽象值操做

  • if(..)語句中的條件判斷表達式

  • for(..;..;..)語句的第二個條件判斷表達式

  • while(..)和do..while(..)的條件判斷表達式

  • ?:中的條件判斷表達式

  • 邏輯運算符||和&&左邊的操做數(a||b等同於a?a:b,a&&b等同於a?b:a)

符號的強制類型轉換

Symbol不能被強制轉換爲數字(顯式和隱式都會產生錯誤),但能夠被強制轉換爲布爾值(顯式和隱式結果都爲true)

寬鬆相等和嚴格相等

==容許在相等的比較中進行強制類型轉換,===則不能容許,並非==檢查值是否相等,===檢查值和類型是否相等

嚴格相等的兩種特殊狀況

  • NaN不等於NaN;

  • +0等於-0;

寬鬆相等之間的隱式轉換

1.字符串和數字之間的相等比較

  • (1) 若是 Type(x) 是數字,Type(y) 是字符串,則返回 x == ToNumber(y) 的結果。

  • (2) 若是 Type(x) 是字符串,Type(y) 是數字,則返回 ToNumber(x) == y 的結果。

const [a, b] = ['42', 42];
console.log(a == b);//true
//等同於
console.log(Number(a) === b);//true

console.log(b == a);//true
//等同於
console.log(Number(a) === b);//true
複製代碼

2.其餘類型和布爾類型之間的比較

  • (1) 若是 Type(x) 是布爾類型,則返回 ToNumber(x) == y 的結果。

  • (2) 若是 Type(y) 是布爾類型,則返回 x == ToNumber(y) 的結果。

const [a, b] = [true, 1];
console.log(a == b);//true
//等同於
console.log(Number(a) === b);//true

console.log(b == a);//true
//等同於
console.log(b === Number(a));//true

const [c, d] = [false, 0];
console.log(c == d);//true
//等同於
console.log(Number(c) === d);//true

console.log(d == c);//true
//等同於
console.log(d === Number(c));//true

console.log('true' == true);//false
//等同於
console.log('true' === 1);//false
複製代碼

3.null和undefined之間的相等比較,規範規定null和undefined寬鬆相等

console.log(null == undefined);//true
複製代碼

4.對象和非對象之間(包括數字、字符串;其中布爾遵循其餘類型和布爾類型之間的比較)的相等比較

  • 若是Type(x)是字符串或者數字,Type(y)是對象,則返回x == ToPromitive(y)的結果;

  • 若是Type(x)是對象,Type(y)是字符串或者數字,則返回ToPromitive(x) == y的結果;

const [x, y] = [['42'], 42];
console.log(x == y);//true
//等同於
console.log(x.toString() == y);//true

const x1 = 'abc';
const y1 = new String(x1);
console.log(x1 == y1);//true
//等同於
console.log(x1 == y1.valueOf());//true
複製代碼

5.一些特殊狀況

const [x, y, z] = [undefined, null, NaN];
console.log(x == Object(x) );//false
console.log(y == Object(y) );//false
//等同於
console.log(x == Object() );//false
console.log(y == Object() );//false

console.log(z == Object(z) );//false
//等同於
console.log(z == new Number(z) );//false

//因爲Objec(undefined)和Object(null)沒有對應的封裝對象,因此不可以被封裝,
//Objec(undefined)和Object(null)均返回常規對象,等同於Object()
//Object(NaN)等同於new Number(NaN), NaN==NaN返回false
複製代碼

6.假值的相等比較

null == '0';//false
null == false;//false
null == '';//false
null == 0;//false

undefined == '0';//false
undefined == false;//false
undefined == '';//false
undefined == 0;//false

null == undefined;//false
//null只會與undefined寬鬆相等


'0' == false;//true ---特殊
'0' == NaN;//false
'0' == 0;//true
'0' == '';//false

false == NaN;//false
false == 0;//true
false == '';//true ---特殊
false == [];//true ---特殊
false == {};//false

'' == NaN;//false
'' == 0;//true ---特殊
'' == [];//true
'' == {};//false

0 == NaN;//false
0 == [];//true ---特殊
0 == {};//false
0 == '\n';//true ---特殊
複製代碼

7.抽象關係比較>、<、≥、≤

  • 若是雙方都是字符串,則按照字母順序進行比較

  • 若是雙方是其餘狀況首先調用ToPrimitive轉換爲基本類型

  • 若是轉換的結果出現非字符串,則根據ToNumber規則強制轉換爲數字進行比較

const a = [42];
const b = ['43'];

console.log(a < b);//true
//ToPrimite轉換
console.log(a.toString() < b.toString());
//按照字母順序判斷
console.log('42' < '43');//true

const a1 = ['42'];
const a2 = ['043'];
console.log(a1 > a2);//true
複製代碼
  • 關於對象關係比較的奇怪現象
var a = { b: 42 };
var b = { b: 43 };
a < b;  // false
a == b; // false
a > b;  // false
a <= b; // true
a >= b; // true
複製代碼

按理兩邊對象都會進行ToPrimitive抽象值操做,轉換爲[object object]應該相等,可是結果卻並不是如此,具體原理參考ECMAScript5規範11.8節

8.原理鞏固

  • 如何讓a==2&&a==3?
const a = new Number('something');
let i = 2;
Number.prototype.valueOf = ()=>i++;
console.log(a == 2 && a == 3);//true
複製代碼
  • [] == ![]爲什麼爲true?

![]首先轉換爲false, [] == false符合上面的假值相等

  • '' == [null]爲什麼爲true?

[null]進行ToPrimitive強制類型轉換爲''

9.安全運用隱式強制類型轉化

  • 若是兩邊的值中有true或者false,千萬不要使用==

  • 若是兩邊的值中有[]、''或者0,儘可能不要使用==

  • 最安全的方式能夠經過typeof判斷

最後慣例,歡迎你們star咱們的人人貸大前端團隊博客,全部的文章還會同步更新到知乎專欄掘金帳號,咱們每週都會分享幾篇高質量的大前端技術文章。若是你喜歡這篇文章,但願能動動小手給個贊。

相關文章
相關標籤/搜索