前言
相信剛開始瞭解js的時候,都會遇到 2 =='2',但 1+2 == 1+'2'為false的情況。這時候應該會是一臉懵逼的狀態,不得不感慨js弱型別的靈活讓人髮指,型別轉換就是這麼猝不及防。結合實際中的情況來看,有意或無意中涉及到型別轉換的情況還是很多的。既然要用到,就需要掌握其原理,知其然重要知其所以然更重要。
js的變數型別
JavaScript 是弱型別語言,意味著JavaScript 變數沒有預先確定的型別。
並且變數的型別是其值的型別。也就是說變數當前的型別由其值所決定,誇張點說上一秒種的string,下一秒可能就是個array了。此外當進行某些操作時,變數可以進行型別轉換,我們主動進行的就是顯式型別轉換,另一種就是隱式型別轉換了。例如:
var a = '1';
typeof a;//string
a =parseInt(a); //顯示轉換為number
typeof a //number
a == '1' //true
複製程式碼
弱型別的特性在給我們帶來便利的同時,也會給我們帶來困擾。趨利避害,充分利用該特性的前提就是掌握型別轉換的原理,下面一起看一下。
js資料型別
老生常談的兩大類資料型別:
- 原始型別
Undefined、 Null、 String、 Number、 Boolean - 引用型別
object
此外還有一個es6新增的Symbol,先不討論它。對於這五類原始型別,突然提問可能想不全,沒必要去死記硬背,可以想一下為否的常見變數及其對應值即可。
0 | Number |
---|---|
'' | String |
false | Boolean |
null | Null |
undefined | Undefined |
對於不同的資料格式轉換規則是不同的,我們需要分別對待。
轉換規則
既然是規範定義的規則,那就不要問為什麼了,先大致看一下,爭取記住。是在不行經常翻翻看看大佬的部落格es5規範。轉換有下面這麼幾類,我們分別看一下具體規範。(這部分轉換規則,完全可以跳過去,看到下面的例項再回頭看應該更容易接受一些)
- 轉換為原始值
- 轉換為數字
- 轉換為字串
ToPrimitive(轉換為原始值)
ToPrimitive 運算子接受一個值,和一個可選的 期望型別 作引數。ToPrimitive 運算子把其值引數轉換為非物件型別。如果物件有能力被轉換為不止一種原語型別,可以使用可選的 期望型別 來暗示那個型別。根據下表完成轉換
這段定義看起來有點枯燥。轉換為原始值,其實就是針對引用資料的,其目的是轉換為非物件型別。
如果已經是原始型別,當然就不做處理了
對於object,返回對應的原始型別,該原始型別是由期望型別決定的,期望型別其實就是我們傳遞的type。直接看下面比較清楚。
ToPrimitive方法大概長這麼個樣子具體如下。
/**
* @obj 需要轉換的物件
* @type 期望轉換為的原始資料型別,可選
*/
ToPrimitive(obj,type)
複製程式碼
type可以為number或者string,兩者的執行順序有一些差別
string:
- 呼叫obj的toString方法,如果為原始值,則返回,否則下一步
- 呼叫obj的valueOf方法,後續同上
- 丟擲TypeError 異常
number:
- 呼叫obj的valueOf方法,如果為原始值,則返回,否則下一步
- 呼叫obj的toString方法,後續同上
- 丟擲TypeError 異常
其實就是呼叫方法先後,畢竟期望資料型別不同,如果是string當然優先呼叫toString。反之亦然。
當然type引數可以為空,這時候type的預設值會按照下面的規則設定
- 該物件為Date,則type被設定為String
- 否則,type被設定為Number
對於Date資料型別,我們更多期望獲得的是其轉為時間後的字串,而非毫秒值,如果為number,則會取到對應的毫秒值,顯然字串使用更多。 其他型別物件按照取值的型別操作即可。
概括而言,ToPrimitive轉成何種原始型別,取決於type,type引數可選,若指定,則按照指定型別轉換,若不指定,預設根據實用情況分兩種情況,Date為string,其餘物件為number。那麼什麼時候會指定type型別呢,那就要看下面兩種轉換方式了。
toNumber
某些特定情況下需要用到ToNumber方法來轉成number 運算子根據下表將其引數轉換為數值型別的值
對於string型別,情況比較多,只要掌握常見的就行了。和直接呼叫Number(str)的結果一致,這裡就不多提了,主要是太多提不完。
需要注意的是,這裡呼叫ToPrimitive的時候,type就指定為number了。下面的toString則為string。
toString
ToString 運算子根據下表將其引數轉換為字串型別的值:
其實瞭解也很簡單,畢竟是個規範,借用大佬一張圖:
雖然是需要死記的東西,還是有些規律可循的。 對於原始值:
- Undefined,null,boolean 直接加上引號,例如'null'
- number 則有比較長的規範,畢竟範圍比較大
常見的就是 '1' NaN則為'NaN' 基本等同於上面一條 對於負數,則返回-+字串 例如 '-2' 其他的先不考慮了。 - 物件則是先轉為原始值,再按照上面的步驟進行處理。
valueOf
當呼叫 valueOf 方法,採用如下步驟:
- 呼叫ToObject方法得到一個物件O
- 原始資料型別轉換為對應的內建物件, 引用型別則不變
- 呼叫該物件(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
複製程式碼
- 左邊取原始值,依舊是Number
- 中間為String,則都進行toString操作
- 左邊轉換按照toString的規則,返回'1'
- 得到結果temp值'12'
- 右邊布林值和temp同樣進行1步驟
- temp為string,則布林值也轉為string'false'
- 拼接兩者 得到最後結果 '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
- 左邊就不說了,number
- 右邊obj轉為基礎型別,按照type為number進行
- 先呼叫valueOf() 得到結果為1
- 兩遍都是number,則進行相加得到2
- 1+obj2
- 左邊為number
- 右邊同樣按照按照type為number進行轉化
- 呼叫obj2.valueOf()得到的不是原始值
- 呼叫toString() return 'a'
- 依據第二條規則,存在string,則都轉換為string進行拼接
- 得到結果1a
- obj1+obj2
- 兩邊都是引用,進行轉換 ToPrimitive 預設type為number
- obj1.valueOf()為1 直接返回
- obj2.valueOf()得到的不是原始值
- 呼叫toString() return 'a'
- 依據第二條規則,存在string,則都轉換為string進行拼接
- 得到結果1a
到這裡相信大家對+這種運算的型別轉換了解的差不多了。下面就看一下另一種隱式型別轉換
== 抽象相等比較
這種比較分為兩大類,
- 型別相同
- 型別不同
相同的就不說了,隱式轉換髮生在不同型別之間。規律比較複雜,規範比較長,這裡也不列舉了,大家可以檢視抽象相等演算法。簡單總結一句,相等比較就不想+運算那樣string優先了,是以number優先順序為最高。概括而言就是,都儘量轉成number來進行處理,這樣也可以理解,畢竟比較還是期望比較數值。那麼規則大概如下:
對於x == y
-
如果x,y均為number,直接比較
沒什麼可解釋的了 1 == 2 //false 複製程式碼
-
如果存在物件,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 複製程式碼
-
存在boolean,按照ToNumber將boolean轉換為1或者0,再進行後面比較
//boolean 先轉成number,按照上面的規則得到1 //3 == 1 false //0 == 0 true 3 == true // false '0' == false //true 複製程式碼
-
如果x為string,y為number,x轉成number進行比較
//'0' toNumber()得到 0 //0 == 0 true '0' == 0 //true 複製程式碼
結束語
參考文章
ECMAScript5.1中文版 + ECMAScript3 + ECMAScript(合集)
你所忽略的js隱式轉換
這篇文章的本意是為自己解惑,寫到後面真的感覺比較乏味,畢竟規範性的東西多一點,不過深入瞭解一下總好過死記硬背。原文請移步我的部落格。對於有些觀點說這些屬於js糟粕,完全不應該深入,怎麼說呢,結合自己情況判斷吧。本人水平有限,拋磚引玉共同學習。