typeof 實現原理
typeof
一般被用於判斷一個變數的型別,我們可以利用 typeof
來判斷number
, string
, object
, boolean
, function
, undefined
, symbol
這七種型別,這種判斷能幫助我們搞定一些問題,比如在判斷不是 object 型別的資料的時候,typeof
能比較清楚的告訴我們具體是哪一類的型別。但是,很遺憾的一點是,typeof
在判斷一個 object的資料的時候只能告訴我們這個資料是 object, 而不能細緻的具體到是哪一種 object, 比如?
let s = new String('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 。用程式碼簡單的表示一下leftValue = Object.__proto__ = Function.prototype; rightValue = Object.prototype; // 第一次判斷 leftValue != rightValue leftValue = Function.prototype.__proto__ = Object.prototype // 第二次判斷 leftValue === rightValue // 返回 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 。我們用程式碼簡單的表示一下
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
方法。