深入瞭解typeof與instanceof的使用場景及注意事項
阿新 • • 發佈:2021-02-09
JavaScript中的資料型別分為兩類,**undefined,number,boolean,string,symbol,bigint,null[^1]組成的基礎型別**和**Object、Function、Array等型別組成的引用型別**。
![在這裡插入圖片描述](https://img2020.cnblogs.com/blog/1604228/202102/1604228-20210209152555923-415978974.png)
如何判斷資料屬於哪種型別是JavaScript中非常重要的一個知識點,其中最常用的兩個方法就是分別使用typeof與instanceof這兩個關鍵字來對資料的型別進行判斷。
typeof與instanceof雖然都可以用來對資料所屬的型別進行判斷,但是它們之間還是存在差異的,而這種差異主要存在於兩個方面:
**1.作用點的不同;**
typeof主要用來判斷基礎資料型別,instanceof則是用來判斷引用資料型別。
**2.底層邏輯的不同;**
typeof是根據資料在儲存單元中的型別標籤來判斷資料的型別,instanceof則是根據函式的prototype屬性值是否存在於物件的原型鏈上來判斷資料的型別。
typeof判斷資料型別共有8個值,它們分別是‘undefined’、‘number’、‘boolean’、‘string’、‘symbol’、‘bigint’、‘object’和‘function’。
使用typeof就可以很好的判斷資料型別undefined、number、boolean、string、symbol和bigint。
不過在判斷基礎型別null時,使用typeof便不再準確了。這個問題的產生可以追溯到JavaScript的第一個版本[^2],在這個版本中,**單個值在棧中佔用32位的儲存單元,而這32位的儲存單元又可以劃分為型別標籤(1-3位)和實際資料,型別標籤儲存於低位中**,具體可以分成5種:
**1.當第0位、第1位和第2位皆為0時,typeof判斷此資料型別為’object’;
2.當第0位為1時,typeof判斷此資料型別為’number(整數)’;
3.當第0位與第2位皆為0,而第1位為1時,typeof判斷此資料型別為’number(浮點數)’;
4.當第0位與第1位皆為0,而第2位為1時,typeof判斷此資料型別為’string’;
5.當第1位與第2位皆為1,而第0位為0時,typeof判斷此資料型別為’boolean’;**
此外還有兩種特殊情況:
**undefined:整數−2^30 (整數範圍之外的數字)
null:第0位到第31位皆為0**
當資料值為null時,正好滿足當第0位、第1位和第2位皆為0時,typeof判斷型別為’object’的條件,所以typeof null === 'object'的結果為true。
使用typeof判斷function也是存在問題的:
在 IE 6, 7 和 8 上,很多宿主物件是物件而不是函式。
例如:
```javascript
typeof alert === 'object';//true
```
還有老版本Firefox中的
```javascript
typeof /[0-9]/ === 'function';//true
```
像這種的還有很多,就不一樣舉例了,多半是瀏覽器實現差異,現在已經統一標準了。
我在ie11上執行的結果:
```javascript
typeof alert === 'function';//true
```
在當前最新版Firefox上執行的結果:
```javascript
typeof reg === 'object';//true
```
typeof在判斷引用型別還存在一些問題,例如:
```javascript
typeof {} === 'object';//true
typeof [] === 'object';//true
typeof window === 'object';//true
typeof new Map() === 'object';//true
```
這個時候如果想要知道更詳細的資訊就需要使用instanceof關鍵字了。
```javascript
alert instanceof Function;//true
({}) instanceof Object;//true
([]) instanceof Array;//true
window instanceof Window;//true
(new Map()) instanceof Map;//true
```
**使用instanceof運算子,我們可以清楚的判斷物件的原型鏈上是否存在函式的prototype屬性值。**
**不過instanceof也並不能完全可信,比如通過Symbol.hasInstance屬性可以影響instanceof的判斷結果:**
```javascript
function Person(){
}
Object.defineProperty(Person,Symbol.hasInstance,{
value : function(){
return false;
}
})
let p = new Person();
p instanceof Person;//false
```
**但是Symbol.hasInstance屬性並不會影響到資料的原型鏈,使用自定義的myInstanceof方法[^3]不會受到Symbol.hasInstance屬性的影響:**
```javascript
/**
* obj 變數
* fn 建構函式
*/
function myInstanceof(obj,fn){
let _prototype = Object.getPrototypeOf(obj);
if(null === _prototype){
return false;
}
let _constructor = _prototype.constructor;
if(_constructor === fn){
return true;
}
return myInstanceof(_prototype,fn);
}
function Person(){
}
Object.defineProperty(Person,Symbol.hasInstance,{
value : function(){
return false;
}
})
let p = new Person();
p instanceof Person;//false
myInstanceof(p,Person);//true
```
**自定義的myInstanceof方法改進版:**
```javascript
/**
* obj 變數
* fn 建構函式
*/
function myInstanceof(obj,fn){
let _prototype = Object.getPrototypeOf(obj);
if(null === _prototype){
return false;
}
let _constructor = _prototype.constructor;
if(_constructor[Symbol.hasInstance]){
return _constructor[Symbol.hasInstance](obj);
}
if(_constructor === fn){
return true;
}
return myInstanceof(_prototype,fn);
}
function Person(){
}
Object.defineProperty(Person,Symbol.hasInstance,{
value : function(){
return false;
}
})
let p = new Person();
p instanceof Person;//false
myInstanceof(p,Person);//false
```
資料和型別不在一個全域性變數下時instanceof也會輸出錯誤的結果
**比方說現在定義兩個html檔案,分別為main.html和iframe.html,程式碼如下:**
==main.html==
```html
main
```
==iframe.html==
```html
iframe
HTMLElement.prototype -> Element.prototype -> Node.prototype -> EventTarget.prototype -> Object.prototype。
然後我們在main.html檔案中,去判斷**p instanceof Object**,也就是判斷 **p instanceof main_window.Object**。p元素是在iframe.html檔案中被構造的,所以**p instanceof iframe_window.Object === true**。如果想讓**p instanceof main_window.Object === true**。
那麼要滿足**iframe_window.Object \=\=\= main_window.Object**。但是這個條件肯定是不能滿足的。如果i**frame_window.Object === main_window.Object**,那麼我在iframe.html檔案中修改Object函式,就會作用到main.html中,這樣會引發很嚴重的安全的問題,以及一系列莫名其妙的bug。
所以不同的全域性變數下的同名建構函式並不是同一個函式,這導致了instanceof在資料與函式位於不同全域性變數下時會判斷出錯。
不過在**這個例子**使用**typeof**倒是可以解決問題,只是要記住判斷資料是不是null型別:
```javascript
null !== p && typeof p === 'object';//true
```
**因為typeof判斷的是儲存單元中的標籤型別,所以不會受到影響。**
## 參考
- [1] [The history of “typeof null”](https://2ality.com/2013/10/typeof-null.html)
- [2] [MDN-typeof](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/typeof)
- [3] [各瀏覽器對typeof運算子的實現差異](https://www.cnblogs.com/snandy/archive/2011/03/18/1988263.html)
[^1]:一般基礎型別我是用小寫字母開頭。
[^2]:此時還沒有Symbol、BigInt,故不在討論範圍內。
[^3]:[myInstanceof方法的由來](https://www.cnblogs.com/liujingjiu/p/14380700.
1
``` **npx http-server開啟main.html後,得到結果p instanceof Object : false** ![圖片](https://img2020.cnblogs.com/blog/1604228/202102/1604228-20210209152556531-921807608.png) **造成這種結果原因在於:** ```javascript iframe.contentWindow.window.Object === window.Object;//false ``` 那瀏覽器要為什麼這樣呢?都用一個Object建構函式不好嗎? 我們這樣看: main.html開啟一個window(我們現在叫它main_window),iframe.html開啟一個window(我們現在叫它iframe_window)。 我們現在從iframe_window中獲取一個p元素物件,它的原型鏈為—HTMLParagraphElement.prototype ->