JS有不少數據類型,對於不一樣數據類型的識別和相互轉換也是面試中的一個常考點,本文主要講的就是類型轉換和類型檢測。javascript
JS中的數據類型主要分爲兩大類:原始類型(值類型)和引用類型。常見的數據類型以下圖所示:java
原始數據類型存在棧中,引用類型在棧中存的是一個引用地址,這個地址指向的是堆中的一個數據對象。須要注意的是null
在這裏咱們算在原始類型裏面,可是你用typeof
的時候會發現他是object
,緣由是就算他是一個對象,那他應該在棧中存一個引用地址,可是他是一個空對象,因此這個地址爲空,也就是不對應堆中的任意一個數據,他在堆中沒有數據,只存在於棧中,因此這裏算爲了原始類型。引用類型其實主要就是Object
,Array
和Function
這些其實也都是Object
派生出來的。關於這兩種類型在內存中的更詳細的知識能夠看這篇文章。git
下面咱們來看看這兩種類型的區別:github
原始類型的值沒法更改,要更改只能從新賦值。像下面這樣嘗試去修改是不行的,可是整個從新賦值能夠。面試
原始類型的比較就是比較值,值相等,他們就相等編程
引用類型的值是能夠修改的,注意這個時候咱們雖然修改了a
裏面的屬性,可是a
在棧上的引用地址並無變化,變化的是堆中的數據。數組
引用類型的比較是比較他們的索引地址,而不是他們的值。好比下面兩個對象,看着是同樣的,可是他們的引用地址不同,實際上是不等的:編程語言
要想讓他們相等,得直接將b
賦值爲a
,這樣他們的引用地址同樣,就是相等的。函數
JS中當不一樣類型的數據進行計算的時候會進行類型轉換,好比下面的例子:post
上面的例子中,咱們用了加減來操做幾個非數字的類型,這時候JS會進行隱式的類型轉換,而後再進行加減運算。除了JS自己的隱式轉換外,有時候咱們還會主動進行類型轉換,這就算是顯示類型轉換了。
常常出如今+
運算中,而且其中有一個操做數不是數值類型
let s = 4 + 'px' + 5; console.log(s); // 4px5 s = 123e-2 + 'a'; console.log(s); // 1.23a
常常出如今數學運算中,表示鏈接字符串的+
運算除外
let s = 'abc'; console.log(+s, -s); // NaN, NaN s = ' 123 '; console.log(+s, -s); // 123 -123 s = new Date(); console.log(+s, -s); // 1588675647421 -1588675647421 (這個操做至關於取毫秒數)
常常出如今if或者邏輯運算中
let s = 'abc'; if(s) { console.log(s); // abc } console.log(!!s); // true
下面的值在進行bool轉換時會轉換爲false
,除此之外都是true
:
當咱們使用==
進行比較時,若是兩邊的類型不一樣,JS會進行類型轉換,而後再比較,===
則不會進行類型轉換,若是===
兩邊的數據類型不一樣,直接返回false
。
上面只是列舉了其中幾種狀況,更多的狀況能夠參考下面這種表,這個表來自於MDN。這個表的內容比較多,有些是規範直接定義的,好比null == undefined
,也沒有太多邏輯可言。咱們不肯定時能夠來查下這個表,可是實際開發中實際上是不建議使用==
的,由於若是你把這個轉換關係記錯了的話可能就會引入比較難排查的bug,通常推薦直接使用===
。
下面這幾張表是一些轉換規則,來自於《JS權威指南》:
顯式類型轉換是咱們本身寫代碼明確轉換的類型,可使代碼看起來更清晰,是實際開發時推薦的作法。
顯式轉換爲字符串可使用toString
方法,它的執行結果一般和String()
方法一致。Number類型的toString
方法還支持參數,能夠指定須要轉換的進制。下面的圖是一些原始類型的toString()
,null
和undefined
沒有toString
方法,調用會報錯:
Number類型的toString
方法支持進制:
轉爲數值就很簡單了,常常在用,就是這兩個全局方法:parseInt
和parseFloat
。
對象轉換爲字符串和數值會稍微麻煩點,下面咱們單獨來探究下。對象轉爲字符串主要有三種方法:
value.toString()
這個前面講過了
'' + value
。這個是前面提到過的隱式轉換,可是value
是對象的話會按照下面的順序進行轉換:
value.valueOf
方法,若是值是原始值,則返回value.toString
方法,若是值是原始值,則返回String(value)
。這個是前面提到的顯式轉換,流程跟前面相似,可是調用toString
和valueOf
的順序不同。
value.toString
方法,若是值是原始值,則返回value.valueOf
方法,若是值是原始值,則返回須要注意的是,Date
對象有點特殊,他始終調用toString
方法。
下面咱們寫一段代碼來驗證下:
Object.prototype.valueOf = function() { return 'aaa'; } Object.prototype.toString = function() { return 'bbb'; } let a = {}; let b = '' + a; let c = String(a); console.log(b); console.log(c);
上述代碼輸出是,跟咱們預期同樣:
對象類型轉爲數值主要有兩種方法:
+value
Number(value)
這兩種的執行邏輯是同樣的:
valueOf
方法,若是值是原始值,就返回toString
方法,而後將toString
的返回值轉換爲數值照例寫個例子看下:
Object.prototype.valueOf = function() { return {}; } Object.prototype.toString = function() { return 'bbb'; } let a = {}; let b = +a; let c = Number(a); console.log(b); console.log(c);
上述代碼的輸出都是NaN
,這是由於咱們toString
方法返回的bbb
沒辦法轉化爲正常數值,強行轉就是NaN
:
類型檢測是咱們常常遇到的問題,面試時也常常問到各類類型檢測的方法,下面是幾種經常使用的類型檢測的方法。
作類型檢測最經常使用的就是typeof
了:
let a; typeof a; // undefined let b = true; typeof b; // boolean let c = 123; typeof c; // number let d = 'abc'; typeof d; // string let e = () => {}; typeof e; // function let f = {}; typeof f; // object let g = Symbol(); typeof g; // symbol
typeof
最簡單,可是他只能判斷基本的類型,若是是對象的話,無法判斷具體是哪一個對象。instanceof
能夠檢測一個對象是否是某個類的實例,這種檢測其實基於面向對象和原型鏈的,更多關於instanceof原理的能夠看這篇文章。下面來看個例子:
let a = new Date(); a instanceof Date; // true
constructor
的原理其實跟前面的instanceof
有點像,也是基於面向對象和原型鏈的。一個對象若是是一個類的實例的話,那他原型上的constructor
其實也就指向了這個類,咱們能夠經過判斷他的constructor
來判斷他是否是某個類的實例。具體的原理在前面提到的文章也有詳細說明。仍是用上面那個例子:
let a = new Date(); a.constructor === Date; // true
使用constructor
判斷的時候要注意,若是原型上的constructor
被修改了,這種檢測可能就失效了,好比:
function a() {} a.prototype = { x: 1 } let b = new a(); b.constructor === a; // 注意這時候是 false
上面爲false
的緣由是,constructor
這個屬性實際上是掛在a.prototype
下面的,咱們在給a.prototype
賦值的時候其實覆蓋了以前的整個prototype
,也覆蓋了a.prototype.constructor
,這時候他其實壓根就沒有這個屬性,若是咱們非要訪問這個屬性,只能去原型鏈上找,這時候會找到Object
:
要避免這個問題,咱們在給原型添加屬性時,最好不要整個覆蓋,而是隻添加咱們須要的屬性,上面的改成:
a.prototype.x = 1;
若是必定要整個覆蓋,記得把constructor
加回來:
a.prototype = { constructor: a, x: 1 }
duck-typing
翻譯叫「鴨子類型」,名字比較奇怪,意思是指一個動物,若是看起來像鴨子,走起路來像鴨子,叫起來也像鴨子,那咱們就認爲他是隻鴨子。就是說咱們經過他的外觀和行爲來判斷他是否是鴨子,而不是準確的去檢測他的基因是否是鴨子。這種方式在科學上固然是不嚴謹的,可是在部分場景下倒是有效的。用編程語言來講,就是看某個對象是否是具備某些特定的屬性和方法,來肯定他是否是咱們要的對象。好比有些開源庫判斷一個對象是否是數組會有下面的寫法:
function isArray(object) { return object !== null && typeof object === 'object' && 'splice' in object && 'join' in object } isArray([]); // true
這就是經過檢測目標對象是否是包含Array應該有的方法來判斷他是否是一個Array。這就是所謂的看着像鴨子,那就是鴨子。可是一個具備splice
和join
方法的對象也能經過這個檢測,因此這樣是不許確的,只是部分場景適用。
Object.prototype.toString.call
是比較準確的,能夠用來判斷原生對象具體是哪一個類型:
Object.prototype.toString.call(new Array()); // [object Array] Object.prototype.toString.call(new Date()); // [object Date]
這個方法返回的是[object XXX]
,這個XXX是對應的構造函數名字。可是他只能檢測原生對象,對於自定義類型是沒有用的:
function a() {} let b = new a(); Object.prototype.toString.call(b); // [object Object]
能夠看到對於自定義類a
的實例b
,咱們獲得仍然是[object Object]
,而不是咱們預期的[object a]
。
JS爲了解決類型檢測的問題,也引入了一些原生方法來提供支持,好比Array.isArray
和Number.isInteger
等。Array.isArray
能夠用來檢測一個對象是否是數組:
Array.isArray([]); // true Array.isArray(123); // false
Number.isInteger
能夠用來檢測一個對象是否是整數:
Number.isInteger(1); // true Number.isInteger(-1); // true Number.isInteger(-1.1); // false Number.isInteger('aaa'); // false
若是有原生檢測的方法咱們固然推薦使用原生方法了,可是目前原生方法並無那麼多和全面,不少時候仍是要用前面的方法來檢測類型。
JS其實沒有一種完美的方法來檢測全部的類型,具體的檢測方法須要咱們根據實際狀況來進行選擇和取捨。下面是幾種方法的總結:
+
,邏輯判斷或者==
時會有隱式的類型轉換。===
,而不是==
。valueOf
和toString
方法,調用順序須要看具體場景。文章的最後,感謝你花費寶貴的時間閱讀本文,若是本文給了你一點點幫助或者啓發,請不要吝嗇你的贊和GitHub小星星,你的支持是做者持續創做的動力。
做者博文GitHub項目地址: https://github.com/dennis-jiang/Front-End-Knowledges