深刻理解 JavaScript 中的類型和類型判斷問題

也許你曾被 js 中的類型判斷搞的暈頭轉向的,下面咱們來從頭梳理一下 js 中的類型判斷問題。javascript

基本類型

JavaScript 中的基本類型有 6 種:html

  • null
  • undefined
  • boolean
  • number
  • string
  • symbol

還有 object。咱們能夠經過 typeof 判斷它們的類型,如:java

typeof null === 'object' // true
typeof undefined === 'undefined' // true
typeof 11 === 'number' // true
...
複製代碼

其中 typeof null === 'object',因此並不能單純的經過 typeof 判斷 null ,這是 js 實現的問題。若是想判斷是不是 null 的話,能夠這樣作:git

var a = null
// 複合判斷
(!a && typeof a === 'object') // true
// 判斷相等性
a === null // true
複製代碼

還有一種狀況就是,對於 function,它是 object 的子類型,可是 typeof function 會返回 function。github

typeof function(){} === 'function' // true
複製代碼

因爲 function 是一個 object,因此 function 能夠擁有普通對象屬性。它的內部有一個 [[Call]] 屬性,使得它能夠被調用。編程

可是判斷 Array,就不能這樣了,雖然 Array 也是 Object 的子類型。函數

typeof [] // object
複製代碼

對於一個變量,若是它沒有值的時候, typeof 會返回 undefined,不然則會返回它的值的類型。對於 undefined 咱們也能夠直接判斷相等性。ui

var a
a === undefined // true
複製代碼

可是 undefined 是能夠被賦值改寫的。爲了防範這種狀況,咱們經過 void 運算符,進行操做,void __ 總會返回 undefinedspa

var a
a === void 0 // true
複製代碼

對於一個未聲明(undeclared)的變量,直接訪問會報錯,可是 typeof 依然會返回 undefinedprototype

nnnnnnnn // Uncaught ReferenceError: nnnnnnnn is not defined
typeof nnnnnnnn // undefined
複製代碼

這個特性能夠幫助咱們作一些防護性編程,由於直接 if(xxxxxx) 會拋錯,可是 if(typeof xxxxxx !== 'undefined') 不會。

寬鬆相等和嚴格相等

你應該會常常聽到不少規範要求你不要用 == 而用 ===,那麼這兩個相等之間有什麼區別呢。簡單的說來就是 == 會發生隱式類型轉化,而 === 不會。

下面是 == 隱式類型轉換的規則:

github.com/aniiantt/kn…

www.ecma-international.org/ecma-262/5.…

== 有時候是會帶來一些便利的,好比說 null 和 undefined 比較是 true。字符串和數字比較字符串會被轉換數字再比較。

比較使人費解的地方是布爾值和其餘類型比較,布爾值先被轉換爲數字 0 或者 1,再進行比較。對象和非對象之間比較,對象會被先轉換爲原始值。

或許你覺得 == 就應該照你想的那樣工做,沒有符合你的預期的話,你會以爲是坑。但其實不是這樣的,它有着一套完善的規則。個人意見是,若是你十分清楚瞭解隱式轉換的規則的話,== 是徹底能夠用的,你知道本身在幹什麼。不然使用 === 。

特殊的值和數字判斷

NaN 表示一個無效的數字,它依然是數字類型:

typeof NaN == 'number' // true
複製代碼

若是你簡單的經過 == 或 NaN 判斷一個數字是不是 NaN:

NaN === NaN // false
NaN !== NaN // true
複製代碼

這樣是不行的!window 有一個全局的函數 isNaN 能夠用來判斷是不是 NaN,它的檢查方式是判斷一個值是否不是一個 NaN,也不是數字。也就是說:

isNaN('aaa') // true
isNaN(NaN) // true
isNaN(100) // false
isNaN('100') // false
複製代碼

ES6 新增 Number.isNaN 會判斷一個數字類型是不是 NaN:

Number.isNaN('aaa') // false
Number.isNaN(NaN) // true
Number.isNaN(100) // false
Number.isNaN('100') // false
複製代碼

除此以外還有一個 Infinity -- 無窮大。 1 / 0 就是 ∞,Infinity。同理 isFinite 能夠判斷是否不是 Infinity,它一樣有一個全局函數和一個 Number 類型的成員函數:

isFinite('aaa') // false
isFinite(NaN) // false
isFinite(Infinity) // false
isFinite('100') // true
isFinite(100) // true
Number.isFinite('aaa') // false
Number.isFinite(NaN) // false
Number.isFinite(100) // false
Number.isFinite('100') // false
Number.isFinite(100) // true
複製代碼

對於零值,咱們來看看一下一個例子:

0 * -1 // -0
複製代碼

+0 -0 的存在是在數學上合理的。但在程序中判斷它們並非那麼容易的,。好在 ES6 提供了 Object.is 函數,咱們能夠用它來處理這些特殊值:

Object.is(0, -0) // false
Object.is(-0, -0) // false
Object.is(NaN, NaN) // true
Object.is(Infinity, Infinity) // true
Object.is(Infinity, -Infinity) // false
複製代碼

詳細見 developer.mozilla.org/zh-CN/docs/…

瞭解知道了這麼多特殊值,那麼咱們該怎麼判斷

內置類型的判斷

如 RegExp Function Date Array 它們是 js 引擎自帶的函數,那麼該怎麼判斷它們的類型呢。

它們內部實現,有一個 [[class]] 屬性,經過 Object.prototype.toString(...),能夠間接訪問到這個屬性,從而實現類型判斷:

Object.prototype.toString.call(/./) // [object RegExp]
Object.prototype.toString.call(() => {}) // [object Function]
Object.prototype.toString.call(new Date()) // [object Date]
Object.prototype.toString.call([]) // [object Array]
複製代碼

你也可使用 instanceof 進行判斷,如 [] instanceof Array === true 可是請注意,這樣在跨宿主環境時會有問題的(如跨 iframe),由於兩個不一樣環境的 Array 對象並非同一個。

類和對象

a instanceof Foo 能夠用來判斷 a 的整條原型鏈 [[prototype]] 中,是否有指向 Foo.prototype 的引用:

function Foo() {}; Foo.prototype.a = '';
function Bar() {}; Bar.prototype = new Foo(); Bar.prototype.b = '';
new Bar instanceof Foo // true
new Bar instanceof Bar // true
複製代碼

可是若是你是採用 Object.create(...) 的方式建立原型鏈,那麼 instanceof 就不是那麼管用了,由於此時並不存在什麼構造函數。

a.isPrototypeOf(b) 能夠判斷 a 是否出如今 b 的原型鏈中。 a instanceof Foo 能夠用 Foo.prototype.isPrototypeOf(a) 代替,這樣更清晰的顯示了他們的關係。

Promise 中是經過檢查返回結果是否具備 then 方法,來判斷返回結果是不是一個 thenable 這樣作很方便,但同時也很脆弱。咱們不得不注意要避免給對象加上 then 屬性,否則你在 Promise 中返回這個對象的時候,會被看成一個 Promise,即便它不是。

參考資料

相關文章
相關標籤/搜索