1. 程式人生 > >深入瞭解typeof與instanceof的使用場景及注意事項

深入瞭解typeof與instanceof的使用場景及注意事項

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

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 ->
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.