typeof 實(shí)現(xiàn)原理
typeof
一般被用于判斷一個(gè)變量的類(lèi)型,我們可以利用 typeof
來(lái)判斷number
, string
, object
, boolean
, function
, undefined
, symbol
這七種類(lèi)型奄妨,這種判斷能幫助我們搞定一些問(wèn)題,比如在判斷不是 object 類(lèi)型的數(shù)據(jù)的時(shí)候条获,typeof
能比較清楚的告訴我們具體是哪一類(lèi)的類(lèi)型。但是官硝,很遺憾的一點(diǎn)是趴生,typeof
在判斷一個(gè) object的數(shù)據(jù)的時(shí)候只能告訴我們這個(gè)數(shù)據(jù)是 object, 而不能細(xì)致的具體到是哪一種 object, 比如??
let s = new String('abc');
typeof s === 'object'// true
s instanceof String // true
復(fù)制代碼
要想判斷一個(gè)數(shù)據(jù)具體是哪一種 object 的時(shí)候,我們需要利用 instanceof
這個(gè)操作符來(lái)判斷凶伙,這個(gè)我們后面會(huì)說(shuō)到郭毕。
來(lái)談?wù)勱P(guān)于 typeof
的原理吧,我們可以先想一個(gè)很有意思的問(wèn)題函荣,js 在底層是怎么存儲(chǔ)數(shù)據(jù)的類(lèi)型信息呢显押?或者說(shuō),一個(gè) js 的變量傻挂,在它的底層實(shí)現(xiàn)中乘碑,它的類(lèi)型信息是怎么實(shí)現(xiàn)的呢?
其實(shí)踊谋,js 在底層存儲(chǔ)變量的時(shí)候蝉仇,會(huì)在變量的機(jī)器碼的低位1-3位存儲(chǔ)其類(lèi)型信息??
- 000:對(duì)象
- 010:浮點(diǎn)數(shù)
- 100:字符串
- 110:布爾
- 1:整數(shù)
but, 對(duì)于 undefined
和 null
來(lái)說(shuō),這兩個(gè)值的信息存儲(chǔ)是有點(diǎn)特殊的殖蚕。
null
:所有機(jī)器碼均為0
undefined
:用 ?2^30 整數(shù)來(lái)表示
所以,typeof
在判斷 null
的時(shí)候就出現(xiàn)問(wèn)題了沉迹,由于 null
的所有機(jī)器碼均為0睦疫,因此直接被當(dāng)做了對(duì)象來(lái)看待。
然而用 instanceof
來(lái)判斷的話??
null instanceof null // TypeError: Right-hand side of 'instanceof' is not an object
復(fù)制代碼
null
直接被判斷為不是 object鞭呕,這也是 JavaScript 的歷史遺留bug蛤育,可以參考typeof。
因此在用 typeof
來(lái)判斷變量類(lèi)型的時(shí)候葫松,我們需要注意瓦糕,最好是用 typeof
來(lái)判斷基本數(shù)據(jù)類(lèi)型(包括symbol
),避免對(duì) null 的判斷腋么。
還有一個(gè)不錯(cuò)的判斷類(lèi)型的方法咕娄,就是Object.prototype.toString,我們可以利用這個(gè)方法來(lái)對(duì)一個(gè)變量的類(lèi)型來(lái)進(jìn)行比較準(zhǔn)確的判斷
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]"
復(fù)制代碼
instanceof 操作符的實(shí)現(xiàn)原理
之前我們提到了 instanceof
來(lái)判斷對(duì)象的具體類(lèi)型珊擂,其實(shí) instanceof
主要的作用就是判斷一個(gè)實(shí)例是否屬于某種類(lèi)型
let person = function () {
}
let nicole = new person()
nicole instanceof person // true
復(fù)制代碼
當(dāng)然圣勒,instanceof
也可以判斷一個(gè)實(shí)例是否是其父類(lèi)型或者祖先類(lèi)型的實(shí)例。
let person = function () {
}
let programmer = function () {
}
programmer.prototype = new person()
let nicole = new programmer()
nicole instanceof person // true
nicole instanceof programmer // true
復(fù)制代碼
這是 instanceof
的用法摧扇,但是 instanceof
的原理是什么呢圣贸?根據(jù) ECMAScript 語(yǔ)言規(guī)范,我梳理了一下大概的思路扛稽,然后整理了一段代碼如下
function new_instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype; // 取右表達(dá)式的 prototype 值
leftVaule = leftVaule.__proto__; // 取左表達(dá)式的__proto__值
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__
}
}
復(fù)制代碼
其實(shí) instanceof
主要的實(shí)現(xiàn)原理就是只要右邊變量的 prototype
在左邊變量的原型鏈上即可吁峻。因此,instanceof
在查找的過(guò)程中會(huì)遍歷左邊變量的原型鏈,直到找到右邊變量的 prototype
用含,如果查找失敗矮慕,則會(huì)返回 false,告訴我們左邊變量并非是右邊變量的實(shí)例耕餐。
看幾個(gè)很有趣的例子
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
復(fù)制代碼
要想全部理解 instanceof
的原理凡傅,除了我們剛剛提到的實(shí)現(xiàn)原理,我們還需要知道 JavaScript 的原型繼承原理肠缔。
關(guān)于原型繼承的原理夏跷,我簡(jiǎn)單用一張圖來(lái)表示
我們知道每個(gè) JavaScript 對(duì)象均有一個(gè)隱式的 __proto__
原型屬性,而顯式的原型屬性是 prototype
明未,只有 Object.prototype.__proto__
屬性在未修改的情況下為 null 值槽华。根據(jù)圖上的原理,我們來(lái)梳理上面提到的幾個(gè)有趣的 instanceof
使用的例子趟妥。
-
Object instanceof Object
由圖可知猫态,Object 的
prototype
屬性是Object.prototype
, 而由于 Object 本身是一個(gè)函數(shù),由 Function 所創(chuàng)建披摄,所以Object.__proto__
的值是Function.prototype
亲雪,而Function.prototype
的__proto__
屬性是Object.prototype
,所以我們可以判斷出疚膊,Object instanceof Object
的結(jié)果是 true 义辕。用代碼簡(jiǎn)單的表示一下leftValue = Object.__proto__ = Function.prototype; rightValue = Object.prototype; // 第一次判斷 leftValue != rightValue leftValue = Function.prototype.__proto__ = Object.prototype // 第二次判斷 leftValue === rightValue // 返回 true 復(fù)制代碼
Function instanceof Function
和Function instanceof Object
的運(yùn)行過(guò)程與Object instanceof Object
類(lèi)似,故不再詳說(shuō)寓盗。 -
Foo instanceof Foo
Foo 函數(shù)的
prototype
屬性是Foo.prototype
灌砖,而 Foo 的__proto__
屬性是Function.prototype
,由圖可知傀蚌,F(xiàn)oo 的原型鏈上并沒(méi)有Foo.prototype
基显,因此Foo instanceof Foo
也就返回 false 。我們用代碼簡(jiǎn)單的表示一下
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 復(fù)制代碼
-
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 復(fù)制代碼
-
Foo instanceof Function
leftValue = Foo, rightValue = Function leftValue = Foo.__proto__ = Function.prototype rightValue = Function.prototype // 第一次判斷 leftValue === rightValue // 返回 true 復(fù)制代碼
總結(jié)
簡(jiǎn)單來(lái)說(shuō)善炫,我們使用 typeof
來(lái)判斷基本數(shù)據(jù)類(lèi)型是 ok 的撩幽,不過(guò)需要注意當(dāng)用 typeof
來(lái)判斷 null
類(lèi)型時(shí)的問(wèn)題,如果想要判斷一個(gè)對(duì)象的具體類(lèi)型可以考慮用 instanceof
销部,但是 instanceof
也可能判斷不準(zhǔn)確摸航,比如一個(gè)數(shù)組,他可以被 instanceof
判斷為 Object舅桩。所以我們要想比較準(zhǔn)確的判斷對(duì)象實(shí)例的類(lèi)型時(shí)酱虎,可以采取 Object.prototype.toString.call
方法。
作者:nicole_zhang18970
鏈接:https://juejin.im/post/6844903613584654344
來(lái)源:掘金
著作權(quán)歸作者所有擂涛。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)读串,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處聊记。