深入瞭解JS型別判斷
關注『開發部落格』公眾號,回覆 加群
JS中判斷資料型別的方式有很多
- typeof
- Object.prototype.toString
- instanceof
- Array.isArray
一、回顧
JS資料型別分為基本型別和引用型別。
基本型別:
- undefined
- null
- Number
- String
- Boolean
- Symbol
引用型別
- Object
- Function
函式是一種特殊的物件,即可呼叫的物件。
二、typeof
2.1 語法
typeof
操作符可以區分基本型別,函式和物件。
console.log(typeof null) // object
console.log(typeof undefined) // undefined
console.log(typeof 1) // number
console.log(typeof 1.2) // number
console.log(typeof "hello") // string
console.log(typeof true) // boolean
console.log(typeof Symbol()) // symbol
console.log(typeof (() => {})) // function
console.log(typeof {}) // object
console.log(typeof []) // object
console.log(typeof /abc/) // object
console.log(typeof new Date()) // object
typeof
有個明顯的bug就是typeof null
為object
;typeof
無法區分各種內建的物件,如Array
,Date
等。
2.2 原理
JS是動態型別的變數,每個變數在儲存時除了儲存變數值外,還需要儲存變數的型別。JS裡使用32位(bit)儲存變數資訊。低位的1~3個bit儲存變數型別資訊,叫做型別標籤(type tag)
.... XXXX X000 // object
.... XXXX XXX1 // int
.... XXXX X010 // double
.... XXXX X100 // string
.... XXXX X110 // boolean
- 只有
int
型別的type tag
使用1個bit,並且取值為1,其他都是3個bit, 並且低位為0。這樣可以通過type tag
低位取值判斷是否為int
資料; - 為了區分
int
,還剩下2個bit,相當於使用2個bit區分這四個型別:object
,double
,string
,boolean
; - 但是
null
,undefined
和Function
並沒有分配type tag
。
如何識別Function
函式並沒有單獨的type tag
,因為函式也是物件。typeof
內部判斷如果一個物件實現了[[call]]
內部方法則認為是函式。
如何識別undefined
undefined
變數儲存的是個特殊值JSVAL_VOID
(0-2^30),typeof
內部判斷如果一個變數儲存的是這個特殊值,則認為是undefined
。
#define JSVAL_VOID INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
如何識別null
null
變數儲存的也是個特殊值JSVAL_NULL
,並且恰巧取值是空指標機器碼(0),正好低位bit的值跟物件的type tag
是一樣的,這也導致著名的bug:
typeof null // object
很不幸,這個bug也不修復了,因為第一版JS就存在這個bug了。祖傳程式碼,不敢修改啊。
有很多方法可以判斷一個變數是一個非null
的物件,之前遇到一個比較經典的寫法:
// 利用Object函式的裝箱功能
function isObject(obj) {
return Object(obj) === obj;
}
isObject({}) // true
isObject(null) // false
三、Object.prototype.toString
一般使用Object.prototype.toString
區分各種內建物件。
3.2 語法
console.log(Object.prototype.toString.call(1)); // [object Number],隱式型別轉換
console.log(Object.prototype.toString.call('')); // [object String],隱式型別轉換
console.log(Object.prototype.toString.call(null)); // [object Null],特殊處理
console.log(Object.prototype.toString.call(undefined)); // [object Undefined],特殊處理
console.log(Object.prototype.toString.call(true)); // [object Boolean],隱式型別轉換
console.log(Object.prototype.toString.call( {})); // [object Object]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call(function(){})); // [object Function]
- 如果實參是個基本型別,會自動轉成對應的引用型別;
Object.prototype.toString
不能區分基本型別的,只是用於區分各種物件;
null
和undefined
不存在對應的引用型別,內部特殊處理了;
3.3 原理
內部屬性[[Class]]
每個物件都有個內部屬性[[Class]]
,內建物件的[[Class]]
的值都是不同的(“Arguments”, “Array”, “Boolean”, “Date”, “Error”, “Function”, “JSON”, “Math”, “Number”, “Object”, “RegExp”, “String”),並且目前[[Class]]
屬性值只能通過Object.prototype.toString
訪問。
Symbol.toStringTag
屬性
其實Object.prototype.toString
內部先訪問物件的Symbol.toStringTag
屬性值拼接返回值的。
var a = "hello"
console.log(Object.prototype.toString.call(a)); // "[object String]"
// 修改Symbol.toStringTag值
Object.defineProperty(String.prototype, Symbol.toStringTag, {
get() {
return 'MyString'
}
})
console.log(Object.prototype.toString.call(a)); // "[object MyString]"
如果哪個貨偷偷修改了內建物件的Symbol.toStringTag
屬性值,那Object.prototype.toString
也就不能正常工作了。
3.4 Object.prototype.toString
內部邏輯
綜上可以總結Object.prototype.toString
的內部邏輯:
-
如果實參是
undefined
, 則返回"[object Undefined]"; -
如果實參是
null
, 則返回"[object Null]"; -
把實參轉成物件
-
獲取物件的
Symbol.toStringTag
屬性值subType
- 如果
subType
是個字串,則返回[object subType]
- 否則獲取物件的
[[Class]]
屬性值type
,並返回[object type]
- 如果
四、instanceof
4.1 語法
object instanceof constructorFunc
instanceof
操作符判斷建構函式constructorFunc
的prototype
屬性是否在物件object
的原型鏈上。
Object.create({}) instanceof Object // true
Object.create(null) instanceof Object // false
Function instanceof Object // true
Function instanceof Function // true
Object instanceof Object // true
- 作為型別判斷的一種方式,
instanceof
操作符不會對變數object
進行隱式型別轉換
"" instanceof String; // false,基本型別不會轉成物件
new String('') instanceof String; // true
- 對於沒有原型的物件或則基本型別直接返回
false
1 instanceof Object // false
Object.create(null) instanceof Object // false
constructorFunc
必須是個物件。並且大部分情況要求是個建構函式(即要具有prototype
屬性)
// TypeError: Right-hand side of 'instanceof' is not an object
1 instanceof 1
// TypeError: Right-hand side of 'instanceof' is not callable
1 instanceof ({})
// TypeError: Function has non-object prototype 'undefined' in instanceof check
({}) instanceof (() => {})
4.2 intanceof
的缺陷
不同的全域性執行上下文的物件和函式都是不相等的,所以對於跨全域性執行上下文intanceof
就不能正常工作了。
<!DOCTYPE html>
<html>
<head></head>
<body>
<iframe src=""></iframe>
<script type="text/javascript">
var iframe = window.frames[0];
var iframeArr = new iframe.Array();
console.log([] instanceof iframe.Array) // false
console.log(iframeArr instanceof Array) // false
console.log(iframeArr instanceof iframe.Array) // true
</script>
</body>
</html>
4.3 原理
Symbol.hasInstance
函式
instanceof
操作符判斷建構函式constructorFunc
的prototype
屬性是否在物件object
的原型鏈上。但是可以利用Symbol.hasInstance
自定義instanceof
操作邏輯。
var obj = {}
// 自定義Symbol.hasInstance方法
Object.defineProperty(obj, Symbol.hasInstance, {
value: function() {
return true;
}
});
1 instanceof obj // true
當然了這個舉例沒有任何實際意義。只是說明下Symbol.hasInstance
的功能。Symbol.hasInstance
本意是自定義建構函式判斷例項物件的方式,不要改變instanceof
的含義。
原型鏈
4.4 instanceof
內部邏輯
綜上可以梳理instanceof
內部邏輯
object instanceof constructorFunc
-
如果
constructorFunc
不是個物件,或則是null
,直接拋TypeError
異常; -
如果
constructorFunc[Symbole.hasInstance]
方法,則返回!!constructorFunc[Symbole.hasInstance](object )
-
如果
constructorFunc
不是函式,直接拋TypeError
異常; -
遍歷
object
的原型鏈,逐個跟constructorFunc.prototype
屬性比較:- 如果
object
沒有原型,則直接返回false
; - 如果
constructorFunc.prototype
不是物件,則直接拋TypeError
異常。
- 如果
五、內建的型別判斷方法
5.1 Array.isArray
ES5引入了方法Array.isArray
專門用於陣列型別判斷。Object.prototype.toString
和instanceof
都不夠嚴格
var arr = []
Object.defineProperty(Array.prototype, Symbol.toStringTag, {
get() {
return 'myArray'
}
})
console.log(Object.prototype.toString.call(arr)); // [object myArray]
console.log(Array.isArray(arr)); // true
console.log(Array.prototype instanceof Array); // false
console.log(Array.isArray(Array.prototype)); // true
不過現實情況下基本都是利用Object.prototype.toString
作為Array.isArray
的polyfill:
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
六、內建物件的prototype
屬性型別判斷
內建的物件Number
, String
, Boolean
, Object
, Function
, Date
, RegExp
, Array
都是各自型別物件的建構函式,並且他們的prototype
屬性都是各自例項物件的原型。但是這些內建物件的prototype
屬性又是什麼型別呢?
6.1 Number.prototype
Number.prototype
也是個數字,類似Number(0)
,但是Number.prototype
並不是Number
的例項。
var prototype = Number.prototype
console.log(prototype == 0); // true
console.log(prototype instanceof Number); // false
console.log(Object.prototype.toString.call(protoype)); // [object Number]
6.2 String.prototype
String.prototype
也是字串,類似""
,但是String.prototype
並不是String
的例項。
var prototype = String.prototype
console.log(prototype == ''); // true
console.log(prototype instanceof String); // false
console.log(Object.prototype.toString.call(prototype)); // [object String]
6.3 Boolean.prototype
Boolean.prototype
也是Boolean,類似false
,但是Boolean.prototype
並不是Boolean
的例項。
var prototype = Boolean.prototype
console.log(prototype == false); // true
console.log(prototype instanceof Boolean); // false
console.log(Object.prototype.toString.call(prototype)); // [object Boolean]
6.4 Object.prototype
Object.prototype
也是Object,類似Object.create(null)
的值(原型為null
的空物件),但是Object.prototype
並不是Object
的例項。
var prototype = Object.prototype
Object.getPrototypeOf(prototype); // null
console.log(prototype instanceof Object); // false
console.log(Object.prototype.toString.call(prototype)); // [object Object]
6.5 Function.prototype
Function.prototype
也是Function,是個空函式,但是Function.prototype
並不是Function
的例項。
var prototype = Function.prototype
console.log(prototype()) // undefined
console.log(prototype instanceof Function); // false
console.log(Object.prototype.toString.call(prototype)); // [object Function]
6.6 Array.prototype
Array.prototype
也是Array,是個空陣列,但是Array.prototype
並不是Array
的例項。
var prototype = Array.prototype
console.log(prototype instanceof Array); // false
console.log(Array.isArray(prototype)) // true
console.log(Object.prototype.toString.call(prototype)); // [object Array]
6.6 RegExp.prototype
RegExp.prototype
並不是RegExp
的例項。但是關於RegExp.prototype
是RegExp
還是物件存在相容性問題,有些瀏覽器下RegExp.prototype
也是RegExp,並且是個總返回true
的正則。
var prototype = RegExp.prototype
console.log(prototype.test('hello')) // true
console.log(prototype instanceof RegExp); // false
// Chrome v84返回"[object Object]", IE返回"[object RegExp]"
console.log(Object.prototype.toString.call(prototype)); //
整理自gitHub筆記:
關注公眾號「前端開發部落格」,回覆1024,領取前端資料包
相關文章
- js判斷型別JS型別
- JS 型別判斷JS型別
- js判斷資料型別JS資料型別
- js判斷裝置型別JS型別
- js中的型別判斷JS型別
- JavaScript中的型別判斷,瞭解一下?JavaScript型別
- js資料型別的判斷JS資料型別
- js資料型別及判斷JS資料型別
- JS判斷PC瀏覽器型別JS瀏覽器型別
- 記一次關於js陣列型別判斷及js型別判斷的細節探索JS陣列型別
- JS資料型別分類和判斷JS資料型別
- JS型別判斷、物件克隆、陣列克隆JS型別物件陣列
- 深入理解 JavaScript 中的型別和型別判斷問題JavaScript型別
- JavaScript 資料型別與型別判斷詳解JavaScript資料型別
- JS靈巧判斷7種型別的方式JS型別
- JS資料型別判斷的幾種方法JS資料型別
- 使用帶型別判斷的比較判斷型別
- 前端基礎——js資料型別及判斷方法前端JS資料型別
- 判斷js中的資料型別的幾種方法JS資料型別
- js判斷瀏覽器型別和作業系統JS瀏覽器型別作業系統
- 最安全的型別判斷型別
- JavaScript判斷資料型別JavaScript資料型別
- 如何判斷變數型別變數型別
- Vim檔案型別判斷型別
- jQuery判斷瀏覽器型別jQuery瀏覽器型別
- 判斷值的資料型別資料型別
- python3 判斷資料型別Python資料型別
- JavaScript的資料型別如何判斷JavaScript資料型別
- python 判斷作業系統型別Python作業系統型別
- 判斷a是否是int型別資料型別
- 如何判斷JavaScript的資料型別?JavaScript資料型別
- js根據字尾判斷檔案檔案型別的程式碼JS型別
- 判斷javaScript變數是Ojbect型別還是Array型別JavaScript變數型別
- [JS基礎] 帶你深入瞭解JS原型JS原型
- JS的判斷語句:判斷、迴圈JS
- javascript中如何判斷變數的型別?JavaScript變數型別
- iOS 使用正則判斷輸入型別iOS型別
- javascript 判斷各種資料的型別JavaScript型別