分享JavaScript 類型判斷的幾種方法
一、JavaScript 基本類型
首先介紹一下JavaScript中的八大基本類型
1、原始數據類型
除對象類型(object)以外的其它任何類型定義的不可變的值(值本身無法被改變)。例如(與 C 語言不同),JavaScript 中字符串是不可變的(譯註:如,JavaScript 中對字符串的操作一定返回瞭一個新字符串,原始字符串並沒有被改變)。我們稱這些類型的值為“原始值”。 —— MDN
(1)佈爾類型(Boolean)
佈爾表示一個邏輯實體,可以有兩個值:true
和false
、
(2)Null類型
- Null 類型隻有一個值:
null
- 值 null 是一個字面量,是表示缺少的標識,隻是變量未指向任何對象。
- 註意:可以把 null 作為尚未創建的對象,但是 null 並非對象。
(3)Undefined類型
- undefined:一個聲明未定義的變量的初始值,或沒有實際參數的形式參數。
undefined
是全局對象的一個屬性。也就是說,它是全局作用域的一個變量。undefined
的最初值就是原始數據類型undefined。
console.log(undefined in window) // true let a; console.log(a) // undefined
- 一個函數如果沒有使用return語句指定返回值,就會返回一個undefined值。
function f() { } console.log(f()) // undefined
(4)數字類型(Number)
- 數字類型是一種基於 IEEE 754 標準的雙精度 64 位二進制格式的值,能表示整數和浮點值
- 值的范圍: Number.MIN_VALUE(5e-324)— Number.MAX_VALUE(1.7976931348623157e+308)
- 如果某個正值大於 Number.MAX_VALUE,則表示為
Infinity
(或者+Infinity)- 如果某個負值小於 -Number.MAX_VALUE則表示為
-Infinity
- 如果某個正值小於 Number.MIN_VALUE,則表示為0(或者+0)
- 如果某個負值大於 -Number.MIN_VALUE則表示為-0
- 如果某個負值小於 -Number.MAX_VALUE則表示為
// 註意:Number.MIN_VALUE(5e-324)— Number.MAX_VALUE(1.7976931348623157e+308) 是可以表示的正值范圍 console.log(Number.MIN_VALUE); // 5e-324 console.log(Number.MAX_VALUE); // 1.7976931348623157e+308 console.log(1e310); // Infinity console.log(-1e310); // -Infinity console.log(1e-330); // 0 console.log(-1e-330); // 0
NaN
(非數值,Not-a–Number)。
not-a-number:
NaN
是一個全局對象的屬性。NaN
屬性的初始值就是 NaN,和Number.NaN
的值一樣。
- 數字類型中隻有一個整數有兩種表示方法:0可表示為-0和+0(0是+0的簡寫)。
(5)BigInt
- BigInt 類型是 JavaScript 中的一個基礎的數值類型,可以表示任意精度的整數。使用 BigInt,您可以安全地存儲和操作大整數,甚至可以超過數字類型的安全整數限制。(BigInt 與 Number 的主要區別在於,BigInt能在內存中精確表示超過安全限制的整數)
// 數字類型安全整數限制 console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991 console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
- BigInt 是通過在整數末尾附加字母
n
或調用構造函數來創建的。
// 構造函數創建 // BigInt() 不與 new 運算符一起使用。 let a = BigInt(1); // 末尾附加字母n創建 let b = 1n; console.log(a) // 1n console.log(b) // 2n
BigInt
不能與數字相互運算。否則,將拋出TypeError
。
console.log(1 + 1n); //Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
(6)字符串類型(String)
JavaScript 的字符串類型用於表示文本數據。它是一組 16 位的無符號整數值的“元素”。在字符串中的每個元素占據瞭字符串的位置。第一個元素的索引為0
,下一個是索引1
,依此類推。字符串的長度是它的元素的數量。
(7)符號類型(Symbols)
- 符號(Symbols)類型是唯一且不可修改的原始值,該類型的性質在於這個類型的值可以用來創建匿名的對象屬性,可以用來作為對象的鍵(key)
- 可以通過調用函數 Symbol() 來創建 Symbol 數據類型實例。
- 當一個 symbol 類型的值在屬性賦值語句中被用作標識符,該屬性(像這個 symbol 一樣)是匿名的;並且是不可枚舉的。因為這個屬性是不可枚舉的,它不會在循環結構 “
for( ... in ...)
” 中作為成員出現,也因為這個屬性是匿名的,它同樣不會出現在 “Object.getOwnPropertyNames()
” 的返回數組裡。這個屬性可以通過創建時的原始 symbol 值訪問到,或者通過遍歷 “Object.getOwnPropertySymbols()
” 返回的數組。在之前的代碼示例中,通過保存在變量myPrivateMethod
的值可以訪問到對象屬性。 - symbol 值提供瞭一種自定義類可以創建私有成員的方式,並維護一個僅適用於該類的 symbol 註冊表。 自定義類可以使用 symbol 值來創建“自有”屬性,這些屬性避免瞭不必要的被偶然發現帶來的影響。 在類定義中,動態創建的 symbol 值將保存到作用域變量中,該變量隻能在類定義中私有地使用。
2、引用數據類型
對象類型(Object)
在計算機科學中, 對象(object)是指內存中的可以被標識符引用的一塊區域。
在 JavaScript 中,對象可以被看作是一組屬性的集合。用對象字面量語法來定義一個對象時,會自動初始化一組屬性。而後,這些屬性還可以被添加和移除。
- 屬性的值可以是任意類型,包括其它對象。
- 屬性使用鍵(key)來標識,它的鍵值可以是一個字符串或者符號值(Symbol)。
對象擁有兩種屬性:
a、數據屬性
b、訪問器屬性
JavaScript中提供瞭很多內置對象,可參考MDN官方文檔。
3、兩種數據類型的存儲方式
(1)原始數據類型:
基礎類型存儲在棧內存,被引用或拷貝時,會創建一個完全相等的變量;占據空間小、大小固定,屬於被頻繁使用數據,所以放入棧中存儲。
(2)引用數據類型:
引用類型存儲在堆內存,存儲的是地址,多個引用指向同一個地址,這裡會涉及一個“共享”的概念;占據空間大、大小不固定。引用數據類型在棧中存儲瞭指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中獲得實體。
所以,在 JavaScript 中,原始類型的賦值會完整復制變量值,而引用類型的賦值是復制引用地址。
二、Javascript 數據類型判斷的幾種方法
1、typeof
typeof
操作符返回一個字符串,表示未經計算的操作數的類型。
語法:
// operand:一個表示對象或原始值的表達式,其類型將被返回 typeof operand typeof(operand)
返回值:
"undefined" "object" "boolean" "number" "bigint" "string" "symbol" "function" "object"
(1)不同數據類型的 typeof 值
// 歷史遺留問題 console.log(typeof null); // object // Undefined 類型typeof值為 undefined console.log(typeof undefined); // undefined // 未定義的變量 typeof值也為 undefined console.log(typeof a); // undefined // 非數值 console.log(typeof NaN); // number // 無窮大 console.log(typeof Infinity); // number // 除 Null 和 Undefined 之外的原始數據類型 typeof 值都是對應的類型 console.log(typeof true); // boolean console.log(typeof 2); // number console.log(typeof 2n); // bigint console.log(typeof BigInt(1)); // bigint console.log(typeof 'str'); // string console.log(typeof Symbol(1)); // symbol console.log(typeof Boolean(true)); // boolean console.log(typeof Number(1)); // number console.log(typeof String('str')); // string // 基本包裝類型 console.log(typeof new Boolean(true)); // object console.log(typeof new Number(1)); // object console.log(typeof new String('str')); // object // 字面量 console.log(typeof {}); // object console.log(typeof []); // object console.log(typeof /1/); // object // 除瞭 Function 對象實例, typeof值都是 object console.log(typeof new Object()); // object console.log(typeof new Array()); // object console.log(typeof new Map()); // object console.log(typeof new Set()); // object console.log(typeof new Date()); // object console.log(typeof new RegExp()); // object console.log(typeof Math); // object // Function 對象實例, typeof值為 function console.log(typeof new Function()); // function console.log(typeof Function); // function console.log(typeof Array); // function console.log(typeof Set); // function console.log(typeof Date); // function console.log(typeof RegExp); // function console.log(typeof Object); // function
(2) typeof null === 'object' 的原因
“typeof null”錯誤是 JavaScript 第一版的遺留物。在這個版本中,值存儲在 32 位單元中,由一個小型標簽(1-3 位)和值的實際數據組成。類型標簽存儲在單元的低位中。
其中有五個:
- 000:對象。數據是對對象的引用。
- 1:整數。數據是一個 31 位有符號整數。
- 010:雙倍。數據是對雙浮點數的引用。
- 100:字符串。數據是對字符串的引用。
- 110:佈爾值。數據是佈爾值。
也就是說,最低位是任一位,那麼類型標簽隻有一位長。或者為零,則類型標簽的長度為三位,為四種類型提供兩個額外的位。
有兩個值是特殊的:
- undefined ( JSVAL_VOID ) 是整數 -2 30(整數范圍之外的數字)。
- null ( JSVAL_NULL ) 是機器碼 NULL 指針。或者:一個對象類型標簽加上一個為零的引用。(表示為全0)
所以 typeof 認為 null 是一個 object
(3)對象類型 typeof 返回值的理解
針對對象類型,個人理解 的是:
- 如果是Function對象, 那麼返回值則是function
- 如果是其他任何普通對象, 那麼返回值則是object,
(4)關於 typeof Boolean(true) 和 typeof new Boolean(true)不同的原因
// 簡單調用Boolean() 函數 console.log(Boolean(true)); // true // 創建一個Boolean對象 console.log(new Boolean(true)); // Boolean {true}
Boolean(true) 的返回值就是Boolean類型的數據,newBoolean(true)返回的是一個Boolean對象。
(5)總結
- typeof 可以判斷除 null 之外所有原始數據類型
- typeof 在判斷實例類型時,根據實例的構造函數返回 object 或 function
- typeof null === 'object' 是一個歷史遺留問題,無法修改。
- typeof NaN === 'number'
2、instanceof
instanceof
運算符用於檢測構造函數的 prototype
屬性是否出現在某個實例對象的原型鏈上
語法:
// object:某個實例對象 // constructor:某個構造函數 object instanceof constructor
(1)相關例子
// instanceof 右邊是一個構造函數,所以undefined 和 null 不能使用instanceof,下面兩條語句執行會報錯 // console.log(undefined instanceof Undefined); // console.log(null instanceof Null); // 對於原始數據類型無法檢測 console.log(2 instanceof Number); // false console.log(1n instanceof BigInt); // false console.log(true instanceof Boolean); // false console.log('str' instanceof String); // false console.log(Symbol(1) instanceof Symbol); // false // 可以檢測內置對象類型 console.log([] instanceof Array); // true console.log(function(){} instanceof Function); // true console.log({} instanceof Object); // true console.log(/1/ instanceof RegExp); // true // 所有函數對象實例原型鏈上都存在Function console.log(Array instanceof Function); // true console.log(Set instanceof Function); // true console.log(Map instanceof Function); // true console.log(Date instanceof Function); // true console.log(RegExp instanceof Function); // true console.log(Function instanceof Function); // true console.log(Object instanceof Function); // true // Object 是所有實例對象原型鏈的盡頭 console.log(Array instanceof Object); // true console.log(Set instanceof Object); // true console.log(Map instanceof Object); // true console.log(Date instanceof Object); // true console.log(RegExp instanceof Object); // true console.log(Function instanceof Object); // true console.log(Object instanceof Object); // true function foo() {} // foo 是 Function的一個實例 console.log(foo instanceof Object); // true console.log(foo instanceof Function); // true // new foo() 是 foo 的一個實例 console.log(new foo() instanceof foo); // true console.log(new foo() instanceof Object); // true console.log(new foo() instanceof Function); // false // foo() 返回的是undefined console.log(foo() instanceof Object); // false console.log(foo() instanceof Function); // false
(2) 當構造函數的proptotype發生改變時,instanceof 結果可能會出錯
function foo() {} let f = new foo(); console.log(f instanceof foo) // true // foo 原型發生改變 foo.prototype = Array.prototype; console.log(f instanceof foo); // false
(3)手動實現 instanceof
function myInstanceof(left, right) { // 獲得構造函數的原型 let prototype = right.prototype // 獲得對象的原型 left = left.__proto__ // 判斷對象的類型是否等於類型的原型 while (true) { // 原型鏈的盡頭是 null,說明實例對象的原型鏈遍歷結束 if (left === null) return false if (prototype === left) return true left = left.__proto__ } }
上述手動實現隻是實現瞭基本功能,但與原生instanceof仍然存在差別,例如:
- 未對 right 進行錯誤處理
// 檢驗right 是否是一個對象(Object) if (right is not Object){ throw new TypeError(" Uncaught TypeError: Right-hand side of 'instanceof' is not Object") } // 檢驗 right 是否可被調用 if (right is not callable) { throw new TypeError(" Uncaught TypeError: Right-hand side of 'instanceof' is not callable") }
- myInstanceof('str', String) === true
console.log(_instanceof('str', String)) // true
有關instanceof 原理,可繼續深入瞭解原型鏈相關知識點。
(4)總結
- instanceof 不能判斷原始數據類型
- instanceof 能夠判斷引用數據類型,但由於是查找原型鏈,所以不能很精準指出數據類型。
例如:
console.log([] instanceof Array); // true console.log([] instanceof Object); // true
- 當構造函數的 prototype 發生改變時,會導致 instanceof 結果發生改變,詳見(2)
所以 instanceof 用於判斷數據類型也存在弊端。
3、構造函數(constructor)
(1)相關例子
// null 和 undefined 沒有構造函數 onsole.log(null.constructor) // TypeError onsole.log(undefined.constructor) // TypeError // 原始數據類型 console.log((2).constructor === Number); // true console.log((2n).constructor === BigInt); // true console.log((true).constructor === Boolean); // true console.log(('str').constructor === String); // true console.log(Symbol(1).constructor === Symbol); // true // 字面量 console.log(([]).constructor === Array); // true console.log((/1/).constructor === RegExp); // true console.log((function() {}).constructor === Function); // true console.log(({}).constructor === Object); // true // JavaScript中的內置函數對象的構造函數為 Function console.log(Array.constructor === Function); // true console.log(String.constructor === Function); // true console.log(Function.constructor === Function); // true console.log(Object.constructor === Function); // true // JavaScript中的內置普通對象的構造函數為 Object console.log(Math.constructor === Object); // true console.log(JSON.constructor === Object); // true console.log(Atomics.constructor === Object); // true console.log(Intl.constructor === Object); // true console.log(Reflect.constructor === Object); // true console.log(WebAssembly.constructor === Object); // true
(2)如果創建一個對象前修改構造函數的原型,會導致constructor不可靠
function Fn(){}; Fn.prototype=new Array(); var f=new Fn(); console.log(f.constructor===Fn); // false console.log(f.constructor===Array); // true
(3)總結
- 通過構造函數 constructor 能夠準確判斷出數據類型,但 null 和 undefined 沒有構造函數,無法判斷
- 內置函數對象的構造函數都是 Function,例如 Array、Map等;內置普通對象的構造函數是Object,例如:JSON、Atomic、Intl、Reflect、WebAssembly 等
- 在創建實例前修改實例的原型,會導致constructor不可靠
所以 constructor 判斷數據類型也存在弊端。
4、Object.prototype.toString.call()
(1)Object.prototype.toString()
每個對象都有一個
toString()
方法,當該對象被表示為一個文本值時,或者一個對象以預期的字符串方式引用時自動調用。默認情況下,toString()
方法被每個Object
對象繼承。如果此方法在自定義對象中未被覆蓋,toString()
返回 "[object type]" ,其中type
是對象的類型 —— MDN
(2) Object.prototype.toString.call() 實例
// 原始數據類型 console.log(Object.prototype.toString.call(undefined)) // [object Undefined] console.log(Object.prototype.toString.call(null)) // [object Null] console.log(Object.prototype.toString.call(1)) // [object Number] console.log(Object.prototype.toString.call(1n)) // [object BigInt] console.log(Object.prototype.toString.call(true)) // [object Boolean] console.log(Object.prototype.toString.call('str')) // [object String] console.log(Object.prototype.toString.call(Symbol(1))) // [object Symbol] // 字面量 console.log(Object.prototype.toString.call({})) // [object object] console.log(Object.prototype.toString.call([])) // [object Array] console.log(Object.prototype.toString.call(/1/)) // [object RegExp] console.log(Object.prototype.toString.call(NaN)) // [object Number] console.log(Object.prototype.toString.call(Infinity)) // [object Number] console.log(Object.prototype.toString.call(globalThis)) // [object Window] // 內置函數對象實例 console.log(Object.prototype.toString.call(new Array())) // [object Array] console.log(Object.prototype.toString.call(new Map())) // [object Map] console.log(Object.prototype.toString.call(new Set())) // [object Set] console.log(Object.prototype.toString.call(new Date())) // [object Date] console.log(Object.prototype.toString.call(new Function())) // [object Function] // 內置普通對象本身 console.log(Object.prototype.toString.call(Math)) // [object Math] console.log(Object.prototype.toString.call(JSON)) // [object JSON] console.log(Object.prototype.toString.call(Reflect)) // [object Reflect] // 內置函數對象本身 console.log(Object.prototype.toString.call(Array)) // [object Function] console.log(Object.prototype.toString.call(Map)) // [object Function] console.log(Object.prototype.toString.call(Function)) // [object Function] console.log(Object.prototype.toString.call(Object)) // [object Function] // 瀏覽器全局對象 console.log(Object.prototype.toString.call(document)) // [object HTMLDocument] console.log(Object.prototype.toString.call(window)) // [object Window] // 自定義構造函數 function Foo() {} f = new Foo(); console.log(Object.prototype.toString.call(f)) // [object object] console.log(Object.prototype.toString.call(Foo)) // [object Function]
(3)基於Object.prototype.toString.call()實現數據類型判斷
function getType(obj){ let type = typeof obj; if (type !== "object") { // 先進行typeof判斷,如果是基礎數據類型,直接返回 return type; } // 對於typeof返回結果是object的,再進行如下的判斷,正則返回結果 return Object.prototype.toString.call(obj).replace(/^[object (\S+)]$/, '$1'); // 註意正則中間有個空格 }
(4)總結
Object.prototype.toString.call() 能準確判斷數據類型。
三、寫在最後
- 除瞭 Null 之外的原始數據類型,均可使用 typeof 判斷數據類型。
- Object.prototype.toString.call() 是判斷數據類型的最好的方法。
- 有關 instanceof 和 constructor , 可以深入去瞭解JavaScript中的原型鏈。
到此這篇關於分享JavaScript 類型判斷的幾種方法的文章就介紹到這瞭,更多相關JavaScript 類型判斷內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- JavaScript中檢測數據類型的四種方法
- JavaScript數據結構常見面試問題整理
- JavaScript原始值與包裝對象的詳細介紹
- 一篇文章弄懂js中的typeof用法
- JavaScript判斷是否為數組的各種方法匯總