JS作類型檢測到底有幾種方法?看完本文就知道了!

JS有不少數據類型,對於不一樣數據類型的識別和相互轉換也是面試中的一個常考點,本文主要講的就是類型轉換和類型檢測。javascript

數據類型

JS中的數據類型主要分爲兩大類:原始類型(值類型)和引用類型。常見的數據類型以下圖所示:java

image-20200506103537269

原始數據類型存在棧中,引用類型在棧中存的是一個引用地址,這個地址指向的是堆中的一個數據對象。須要注意的是null在這裏咱們算在原始類型裏面,可是你用typeof的時候會發現他是object,緣由是就算他是一個對象,那他應該在棧中存一個引用地址,可是他是一個空對象,因此這個地址爲空,也就是不對應堆中的任意一個數據,他在堆中沒有數據,只存在於棧中,因此這裏算爲了原始類型。引用類型其實主要就是ObjectArrayFunction這些其實也都是Object派生出來的。關於這兩種類型在內存中的更詳細的知識能夠看這篇文章。git

image-20200506104330145

下面咱們來看看這兩種類型的區別:github

原始類型

  1. 原始類型的值沒法更改,要更改只能從新賦值。像下面這樣嘗試去修改是不行的,可是整個從新賦值能夠。面試

    image-20200506104958681

    image-20200506105044457

  2. 原始類型的比較就是比較值,值相等,他們就相等編程

引用類型

  1. 引用類型的值是能夠修改的,注意這個時候咱們雖然修改了a裏面的屬性,可是a在棧上的引用地址並無變化,變化的是堆中的數據。數組

    image-20200506105513907

  2. 引用類型的比較是比較他們的索引地址,而不是他們的值。好比下面兩個對象,看着是同樣的,可是他們的引用地址不同,實際上是不等的:編程語言

    image-20200506110135018

    要想讓他們相等,得直接將b賦值爲a,這樣他們的引用地址同樣,就是相等的。函數

    image-20200506110256501

類型轉換

JS中當不一樣類型的數據進行計算的時候會進行類型轉換,好比下面的例子:post

image-20200506110621714

上面的例子中,咱們用了加減來操做幾個非數字的類型,這時候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   (這個操做至關於取毫秒數)

轉爲bool的場景

常常出如今if或者邏輯運算中

let s = 'abc';
if(s) {
  console.log(s);  // abc
}

console.log(!!s);  // true

下面的值在進行bool轉換時會轉換爲false,除此之外都是true:

  1. 0
  2. NaN
  3. ''(空字符串)
  4. null
  5. undefined

==運算符

當咱們使用==進行比較時,若是兩邊的類型不一樣,JS會進行類型轉換,而後再比較,===則不會進行類型轉換,若是===兩邊的數據類型不一樣,直接返回false

image-20200506112606774

上面只是列舉了其中幾種狀況,更多的狀況能夠參考下面這種表,這個表來自於MDN。這個表的內容比較多,有些是規範直接定義的,好比null == undefined,也沒有太多邏輯可言。咱們不肯定時能夠來查下這個表,可是實際開發中實際上是不建議使用==的,由於若是你把這個轉換關係記錯了的話可能就會引入比較難排查的bug,通常推薦直接使用===

image-20200506111718423

轉換規則

下面這幾張表是一些轉換規則,來自於《JS權威指南》:

image-20200505185955549

image-20200505190049837

image-20200505190124871

顯式類型轉換

顯式類型轉換是咱們本身寫代碼明確轉換的類型,可使代碼看起來更清晰,是實際開發時推薦的作法。

image-20200506113002845

轉字符串

顯式轉換爲字符串可使用toString方法,它的執行結果一般和String()方法一致。Number類型的toString方法還支持參數,能夠指定須要轉換的進制。下面的圖是一些原始類型的toString()nullundefined沒有toString方法,調用會報錯:

image-20200506113217062

Number類型的toString方法支持進制:

image-20200506113346662

轉數值

轉爲數值就很簡單了,常常在用,就是這兩個全局方法:parseIntparseFloat

對象轉字符串

對象轉換爲字符串和數值會稍微麻煩點,下面咱們單獨來探究下。對象轉爲字符串主要有三種方法:

  1. value.toString()

    這個前面講過了

  2. '' + value。這個是前面提到過的隱式轉換,可是value是對象的話會按照下面的順序進行轉換:

    1. 先調用value.valueOf方法,若是值是原始值,則返回
    2. 不然調用value.toString方法,若是值是原始值,則返回
    3. 不然報錯TypeError
  3. String(value)。這個是前面提到的顯式轉換,流程跟前面相似,可是調用toStringvalueOf的順序不同。

    1. 先調用value.toString方法,若是值是原始值,則返回
    2. 不然調用value.valueOf方法,若是值是原始值,則返回
    3. 不然報錯TypeError

須要注意的是,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);

上述代碼輸出是,跟咱們預期同樣:

image-20200506160225229

對象轉數值

對象類型轉爲數值主要有兩種方法:

  1. +value
  2. Number(value)

這兩種的執行邏輯是同樣的:

  1. 先調用valueOf方法,若是值是原始值,就返回
  2. 不然,調用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:

image-20200506160750545

類型檢測

類型檢測是咱們常常遇到的問題,面試時也常常問到各類類型檢測的方法,下面是幾種經常使用的類型檢測的方法。

typeof

作類型檢測最經常使用的就是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

instanceof

typeof最簡單,可是他只能判斷基本的類型,若是是對象的話,無法判斷具體是哪一個對象。instanceof能夠檢測一個對象是否是某個類的實例,這種檢測其實基於面向對象和原型鏈的,更多關於instanceof原理的能夠看這篇文章。下面來看個例子:

let a = new Date();
a instanceof Date;  // true

constructor

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:

image-20200506172606821

要避免這個問題,咱們在給原型添加屬性時,最好不要整個覆蓋,而是隻添加咱們須要的屬性,上面的改成:

a.prototype.x = 1;

若是必定要整個覆蓋,記得把constructor加回來:

a.prototype = {
  constructor: a,
  x: 1
}

duck-typing

duck-typing翻譯叫「鴨子類型」,名字比較奇怪,意思是指一個動物,若是看起來像鴨子,走起路來像鴨子,叫起來也像鴨子,那咱們就認爲他是隻鴨子。就是說咱們經過他的外觀和行爲來判斷他是否是鴨子,而不是準確的去檢測他的基因是否是鴨子。這種方式在科學上固然是不嚴謹的,可是在部分場景下倒是有效的。用編程語言來講,就是看某個對象是否是具備某些特定的屬性和方法,來肯定他是否是咱們要的對象。好比有些開源庫判斷一個對象是否是數組會有下面的寫法:

function isArray(object) {
  return object !== null && 
    typeof object === 'object' && 
    'splice' in object && 
    'join' in object
}

isArray([]);  // true

這就是經過檢測目標對象是否是包含Array應該有的方法來判斷他是否是一個Array。這就是所謂的看着像鴨子,那就是鴨子。可是一個具備splicejoin方法的對象也能經過這個檢測,因此這樣是不許確的,只是部分場景適用。

Object.prototype.toString.call

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]

一些原生方法: Array.isArray,Number.isInteger

JS爲了解決類型檢測的問題,也引入了一些原生方法來提供支持,好比Array.isArrayNumber.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其實沒有一種完美的方法來檢測全部的類型,具體的檢測方法須要咱們根據實際狀況來進行選擇和取捨。下面是幾種方法的總結:

image-20200506180011564

總結

  1. JS有兩種數據類型,原始類型和引用類型,引用類型主要就是對象。
  2. 當咱們使用+,邏輯判斷或者==時會有隱式的類型轉換。
  3. 有時候隱式的類型轉換會出現咱們不想要的結果,若是咱們肯定要進行判斷或者類型轉換,最好使用顯式的,好比使用===,而不是==
  4. 對象轉爲字符串和數值可能須要調valueOftoString方法,調用順序須要看具體場景。
  5. JS沒有一個完美的類型檢測方法,咱們最好根據須要選擇具體的檢測方法。

文章的最後,感謝你花費寶貴的時間閱讀本文,若是本文給了你一點點幫助或者啓發,請不要吝嗇你的贊和GitHub小星星,你的支持是做者持續創做的動力。

做者博文GitHub項目地址: https://github.com/dennis-jiang/Front-End-Knowledges

做者掘金文章彙總:https://juejin.im/post/5e3ffc85518825494e2772fd

相關文章
相關標籤/搜索