值類型轉換
JavaScript中的值類型轉換分兩種
類型轉換(顯式,發生在編譯階段)
var a = 42;
var b = String(a);
console.log(typeof b); //string
強制類型轉換(隱式,發生在運行階段)
var a = 42;
var b = a + "";
console.log(typeof b); //string
但是不管是哪一種,返回的都是標量基本類型值,不會返回函數和對象(因此我們之前說過的封裝不是嚴格意義上的類型轉換)
抽象值操作
1. ToString
基本類型值的字符串化的規則爲:
- null,undefined,true等基本類型值分別轉換爲他們各自對應的"null","undefined","true"等
- 數字的字符串化遵循通用規則,但是極大極小的數字使用指數形式,如"1.07e21"等
- 對於普通對象來說,除非自己定義,否則toString()(Object.prototype.toString())會返回內部屬性[[class]]的值如"[object Object]"(函數返回的是類似於"function(){/../}"的字符串)
- 數組的默認toString()方法經過了重新定義,所有單元字符串化後再用","連接,如[1,2,3]轉換爲"1,2,3"
toString()可以被顯式調用,也可以在字符串化的時候自動調用
JSON字符串化
(JSON.stringify(..)並不是強制類型轉換,只是許多規則與ToString相同)
JSON.stringify(..)將JSON對象序列化爲字符串時的規則
- 對於大多數簡單值(數字,字符串,布爾值)來說效果和toString()相同,但是如果括號裏輸入的是帶雙引號的字符串,那麼結果也是帶雙引號的字符串
- undefined,function,symbol和包含循環引用(對象之間互相引用)的對象都不符合JSON的結構標準,因此JSON無法處理他們,遇到這類值的時候JSON.stringify()會忽略他們,在數組中則會返回null來保證單元位置不變(對包含循環引用的對象執行則會出錯)
2. ToNumber
簡單的轉換
- true轉換爲1
- false轉換爲0
- undefined轉換爲NaN
- null轉換爲0
- 0開頭的十六進制按照十進制處理
在轉換對象的時候抽象操作ToPrimitive會首先通過內部操作DefaultValue
檢查是否有valueOf方法,如果有並返回一個基本類型值,如果沒有則對toString的值進行轉化
如果valueOf和toString都不返回基本類型值,則會產生TypeError錯誤
3. ToBoolean
JavaScript中的假值
- undefined
- null
- false
- +0,-0和NaN
- ""
遇到這些值時,強制類型轉換會返回false,其他都爲true
字符串和數字的顯式轉換
字符串和數字的轉換通常通過String()和Number()進行(不帶New,因此並不會創建封裝對象)
var a = 42;
var b = String(a);
console.log(typeof b); //string
var a = "42";
var b = Number(a);
console.log(typeof b); //number
除了以上兩種還有其他方式可以實現同樣的效果
1. toString()
toString方法首先自動爲42創建了一個封裝對象然後再調用Object.prototype中的toString方法
var a = 42;
var b = a.toString();
console.log(typeof b); //string
2. +運算符
本例中的+a是+的一元形式(使用-也可以進行轉化,但是會改變值),使用這種形式轉化的數值進行運算的時候記得帶空格(如+ +a)以區分++運算符
var a = "42";
var b = +a;
console.log(typeof b); //number
比較常見的用法有
var time = new Date();
console.log(time, typeof time); //Mon Sep 24 2018 15:42:41 GMT+0800 (中國標準時間) "object"
console.log(+time, typeof + time); //1537775001102 "number"
本例中將date對象強制轉換爲數字,返回一個Unix時間戳,值爲從1970年一月一日00:00:00 UTC到當前時間的毫秒數
由於使用構造函數時,構造函數無參數是可以省略()的,因此可能會遇到+new Date這種用法
3.~運算符
~運算符跟以上兩種運算符稍微有些不同
~作爲位運算符首先有一個特點就是隻適用於32位整數
因此~會先執行一個抽象操作ToInt32
而ToInt32又會先執行ToNumber進行一個強制類型轉換然後再執行ToInt32
var a = "42";
var b = ~a;
console.log(typeof b, b); //number -43
~x大致等同於 -(x+1)
因此只有當x=-1的時候~x纔會返回0(~是字位操作而不是數學運算,所以並不會返回-0)
-1是一個哨位值,被賦予了一個特殊含義,比如JavaScript中的indexOf(..)函數,返回-1表示不存在
但是直接使用>=0或者== -1進行判斷的話存在一個抽象泄露的問題(暴露了底層的實現細節)
因此有個更好的方法是使用~運算符
~和indexOf(..)一起可以將結果強制類型轉換爲真/假值
當indexOf(..)返回-1的時候~運算符將-1轉換爲假值0,其他情況一律爲真
比如 if(~a.indexOf("a")) 判斷 a字符串中是否存在"a"這一個子字符串
~~進行字位截除
由於~字位運算符只適用於32位整數
因此可以是用來進行除去數字值的小數部分
第一個~執行ToInt32後進行字位反轉,第二個~再次進行同樣的步驟,因此結果其實是執行了ToInt32的原值
var a = "42.23333";
var b = ~~a;
console.log(b); //42
但是需要注意,這種方法只適用於32位數,且不完全等同於Math.floor(..)(向下取整)
var a = "-42.23333";
var b = ~~a;
console.log(b,Math.floor(a)); //-42 -43
事實上使用0|a(或運算符,對二進制進行或運算)也能達到一樣的效果,而且看起來還更簡便一些,因爲|運算符的空操作(0|x)只進行了ToInt32操作
var a = "-42.23333";
var b = a | 0;
console.log(b); //-42
但是如果考慮到優先級的問題的話,我還是更傾向於使用~~運算符
~~20 / 10 //2
20 | 0 / 10 //20
(20 | 0) / 10 //2
顯式解析數字字符串
Number("42"),//42
Number("42px"),//NaN
parseInt("42"),//42
parseInt("42px"),//42
解析字符串允許字符串中含有非數字字符,解析按照左到右,遇到非數字字符停止
轉換不允許出現非數字字符,否則失敗並返回NaN
parseInt()針對的是字符串值,如果傳入非字符串,那麼會被強制轉換爲字符串
在ES5之前的parseInt()有一個大坑
如果沒有第二個參數來指定轉換的基數,那麼會根據字符串中的第一個字符來決定
如果第一個字符是x或者X則轉換爲16進制
如果第一個字符是0,則轉換爲八進制
將第二個參數設定爲10則可以避免該問題
ES5以後默認轉換爲10進制,除非另外指定
解析非字符串
parseInt(1 / 0, 19) //18
看到這個例子是不是覺得有點無法理解?
解析非字符串的時候,首先會把參數強制轉換爲字符串再解析
1/0的結果是Infinity,而JavaScript中所有的值都有一個默認的字符串形式,所以可以直接轉爲"Infinity"
再回到基數19,代表有效數字範圍爲0-9和a-i
這個時候解析到"I"的時候是沒問題的,但是接下來的"n"不在有效數字範圍內,就像上面遇到了"42px"中的"p"一樣
解析停止,返回的是"I",也就是18
此外還有一些看起來奇怪但是沒有問題的例子
console.log(
parseInt(0.000008), //0,來自於0.000008中的0
parseInt(0.0000008), //8,來自於8e-7中的8
parseInt(false, 16), //250,來自於false中的fa
parseInt(parseInt, 16), //15,來自function..中的f
parseInt("0x10"), //16,來自於0x10
parseInt("103", 2), //2,來自於103中的10
)
顯示轉換爲布爾值
和上面的String()和Number()一樣Boolean(..)(不帶new)也是顯式的ToBoolean強制類型轉換,但是這種方法並不常用
與++轉換number類型類似,!!同樣也可以用來轉換boolean類型
console.log(
!!"0", //true
!![], //true
!!{}, //true
!!"", //false
!!0, //false
!!null, //false
!!undefined //false
)
在if(..)這樣的布爾值上下文中,如果沒有使用Boolean(..)和!!,就會自動進行ToBoolean轉換