typeof 實現原理
typeof
一般被用於判斷如number
, string
, object
, boolean
, function
, undefined
, symbol
這些類型,在非object類型數據時,type of返回的結果比較清楚。但是 在判斷object類型數據的時候只能告訴我們這個數據的類型是 object,而不能告訴我們具體是哪一種object
那麼,typeof
是如何判斷這些類型的呢?其實是直接去判斷了js底層存儲變量類型的信息:
在 js 的最初版本中,使用的是 32 位系統,爲了性能考慮使用低位存儲了變量的類型信息,會在變量的機器碼的低位1-3位存儲其類型信息,如:
- 000:對象
- 010:浮點數
- 100:字符串
- 110:布爾
- 1:整數
對於 undefined
和 null
來說,這兩個值的信息存儲是有點特殊的:
- null:所有機器碼均爲0
- undefined:用 −2^30 整數來表示
所以一個js的遺留bug原因我們也知道了,就是 typeof 在判斷 null 的時候出現問題,由於 null 的所有機器碼均爲0,因此直接被當做了對象來看待
因此,在使用typeof
判斷類型時,最好使用用來判斷基本數據類型,避免對null
進行判斷,object
由於不夠準確,也不推薦使用。
Object.prototype.toString.call()
如果我們想獲得一個變量的正確類型,可以通過 Object.prototype.toString.call(xx)
。這樣我們就可以獲得類似 [object Type]
的字符串
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
主要的實現原理就是隻要右邊變量的 prototype
在左邊變量的原型鏈上即可。因此,instanceof
在查找的過程中會遍歷左邊變量的原型鏈,直到找到右邊變量的 prototype
,如果查找失敗,則會返回 false
,告訴我們左邊變量並非是右邊變量的實例。
大致原理用一段代碼表示爲
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
實現原理,還需要知道 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 。用代碼簡單的表示一下
leftValue = Object.__proto__ = Function.prototype;
rightValue = Object.prototype;
// 第一次判斷
leftValue != rightValue
leftValue = Function.prototype.__proto__ = Object.prototype
// 第二次判斷
leftValue === rightValue
// 返回 true
- Foo instanceof Foo
Foo 函數的 prototype 屬性是 Foo.prototype,而 Foo 的 proto 屬性是 Function.prototype,由圖可知,Foo 的原型鏈上並沒有 Foo.prototype ,因此 Foo instanceof Foo 也就返回 false 。
leftValue = Foo, rightValue = Foo
leftValue = Foo.__proto = Function.prototype
rightValue = Foo.prototype
// 第一次判斷
leftValue != rightValue
leftValue = Function.prototype.__proto__ = Object.prototype
// 第二次判斷
leftValue != rightValue
leftValue = Object.prototype = null
// 第三次判斷
leftValue === null
// 返回 false
- Foo instanceof Object
leftValue = Foo, rightValue = Object
leftValue = Foo.__proto__ = Function.prototype
rightValue = Object.prototype
// 第一次判斷
leftValue != rightValue
leftValue = Function.prototype.__proto__ = Object.prototype
// 第二次判斷
leftValue === rightValue
// 返回 true
- Foo instanceof Function
leftValue = Foo, rightValue = Function
leftValue = Foo.__proto__ = Function.prototype
rightValue = Function.prototype
// 第一次判斷
leftValue === rightValue
// 返回 true
總結
簡單來說,我們使用 typeof
來判斷基本數據類型是 ok 的,不過需要注意當用 typeof
來判斷 null
類型時的問題,如果想要判斷一個對象的具體類型可以考慮用 instanceof
,但是 instanceof
也可能判斷不準確,比如一個數組,他可以被 instanceof
判斷爲 Object
。所以我們要想比較準確的判斷對象實例的類型時,可以採取 Object.prototype.toString.call
方法
參考文章:https://juejin.im/post/5b0b9b9051882515773ae714