淺談 instanceof 和 typeof 的實現原理
轉自:https://juejin.cn/post/6844903613584654344
typeof 實現原理
typeof
一般被用於判斷一個變數的型別,我們可以利用typeof
來判斷number
,string
,object
,boolean
,function
,undefined
,symbol
這七種型別,
這種判斷能幫助我們搞定一些問題,比如在判斷不是 object 型別的資料的時候,
typeof
一般被用於判斷一個變數的型別,我們可以利用typeof
來判斷number
,string
,object
,boolean
,function
,undefined
,symbol
這七種型別,typeof
能比較清楚的告訴我們具體是哪一類的型別。但是,很遺憾的一點是,typeof
在判斷一個 object的資料的時候只能告訴我們這個資料是 object, 而不能細緻的具體到是哪一種 object, 比如
let s = newString('abc'); typeof s === 'object'// true s instanceof String // true
要想判斷一個數據具體是哪一種 object 的時候,我們需要利用instanceof
這個操作符來判斷,這個我們後面會說到。
來談談關於typeof
的原理吧,我們可以先想一個很有意思的問題,js 在底層是怎麼儲存資料的型別資訊呢?或者說,一個 js 的變數,在它的底層實現中,它的型別資訊是怎麼實現的呢?
其實,js 在底層儲存變數的時候,會在變數的機器碼的低位1-3位儲存其型別資訊
- 000:物件
- 010:浮點數
- 100:字串
- 110:布林
- 1:整數
but, 對於undefined
null
來說,這兩個值的資訊儲存是有點特殊的。
null
:所有機器碼均為0
undefined
:用 −2^30 整數來表示
所以,typeof
在判斷null
的時候就出現問題了,由於null
的所有機器碼均為0,因此直接被當做了物件來看待。
然而用instanceof
來判斷的話
null instanceof null // TypeError: Right-hand side of 'instanceof' is not an object
null
直接被判斷為不是 object,這也是 JavaScript 的歷史遺留bug,可以參考typeof。
因此在用typeof
來判斷變數型別的時候,我們需要注意,最好是用
typeof
來判斷基本資料型別(包括symbol
),避免對 null 的判斷。
還有一個不錯的判斷型別的方法,就是Object.prototype.toString,我們可以利用這個方法來對一個變數的型別來進行比較準確的判斷
Object.prototype.toString.call(1) // "[object Number]" Object.prototype.toString.call('hi') // "[object String]" Object.prototype.toString.call({a:'hi'}) // "[object Object]" Object.prototype.toString.call([1,'a']) // "[object Array]" Object.prototype.toString.call(true) // "[object Boolean]" Object.prototype.toString.call(() => {}) // "[object Function]" Object.prototype.toString.call(null) // "[object Null]" Object.prototype.toString.call(undefined) // "[object Undefined]" Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"
instanceof 操作符的實現原理
之前我們提到了instanceof
來判斷物件的具體型別,其實instanceof
主要的作用就是判斷一個例項是否屬於某種型別
let person = function () { } let nicole = new person() nicole instanceof person // true
當然,instanceof
也可以判斷一個例項是否是其父型別或者祖先型別的例項。
let person = function () { } let programmer = function () { } programmer.prototype = new person() let nicole = new programmer() nicole instanceof person // true nicole instanceof programmer // true
這是instanceof
的用法,但是instanceof
的原理是什麼呢?根據 ECMAScript 語言規範,我梳理了一下大概的思路,然後整理了一段程式碼如下
function new_instance_of(leftVaule, rightVaule) { let rightProto = rightVaule.prototype; // 取右表示式的 prototype 值 leftVaule = leftVaule.__proto__; // 取左表示式的__proto__值 while (true) { if (leftVaule === null) { return false; } if (leftVaule === rightProto) { return true; } leftVaule = leftVaule.__proto__ } }其實
instanceof
主要的實現原理就是隻要右邊變數的prototype
在左邊變數的原型鏈上即可。因此,instanceof
在查詢的過程中會遍歷左邊變數的原型鏈,直到找到右邊變數的prototype
,如果查詢失敗,則會返回 false,告訴我們左邊變數並非是右邊變數的例項。
看幾個很有趣的例子
function Foo() { } Object instanceof Object // true Function instanceof Function // true Function instanceof Object // true Foo instanceof Foo // false Foo instanceof Object // true Foo instanceof Function // true
要想全部理解instanceof
的原理,除了我們剛剛提到的實現原理,我們還需要知道 JavaScript 的原型繼承原理。
關於原型繼承的原理,我簡單用一張圖來表示
我們知道每個 JavaScript 物件均有一個隱式的__proto__
原型屬性,而顯式的原型屬性是prototype
,只有Object.prototype.__proto__
屬性在未修改的情況下為 null 值。根據圖上的原理,我們來梳理上面提到的幾個有趣的instanceof
使用的例子。
Object instanceof Object
由圖可知,Object 的prototype
屬性是Object.prototype
, 而由於 Object 本身是一個函式,由 Function 所建立,所以Object.__proto__
的值是Function.prototype
,而Function.prototype
的__proto__
屬性是Object.prototype
,所以我們可以判斷出,Object instanceof Object
的結果是 true 。
Function instanceof Function
和Function instanceof Object
的執行過程與Object instanceof Object
類似,故不再詳說。
Foo instanceof Foo
Foo 函式的prototype
屬性是Foo.prototype
,而 Foo 的__proto__
屬性是Function.prototype
,由圖可知,Foo 的原型鏈上並沒有Foo.prototype
,因此Foo instanceof Foo
也就返回 false 。
Foo instanceof Object 、Foo instanceof Function 類似。
總結
簡單來說,我們使用typeof
來判斷基本資料型別是 ok 的,不過需要注意當用typeof
來判斷null
型別時的問題,如果想要判斷一個物件的具體型別可以考慮用instanceof
,但是instanceof
也可能判斷不準確,比如一個數組,他可以被instanceof
判斷為 Object。所以我們要想比較準確的判斷物件例項的型別時,可以採取Object.prototype.toString.call
方法。
instanceof
主要的實現原理就是隻要右邊變數的prototype
在左邊變數的原型鏈上即可。因此想要準確判斷是否屬於哪個類可以使用instance.__proto__===Class.prototype,如:
function A(){} var a = new A(); a.__proto__===A.prototype//true