深入探究JavaScript的類型判斷(從基礎(chǔ)到精通)
JavaScript 語(yǔ)言具有多種數(shù)據(jù)類型,它們可以大致分為兩大類:基本數(shù)據(jù)類型(Primitive Data Types)和引用數(shù)據(jù)類型(Reference Data Types)。
一、數(shù)據(jù)類型
基本數(shù)據(jù)類型(Primitive Data Types) 包括:
- Undefined: 表示變量已被聲明但未被初始化時(shí)的值。
- Null: 代表一個(gè)刻意的空值或缺失的值。
- Boolean: 只有兩個(gè)值,
true
或false
。 - Number: 用于表示整數(shù)和浮點(diǎn)數(shù),包括Infinity、-Infinity和NaN。
- String: 用于表示文本,由零個(gè)或多個(gè)字符組成。
- Symbol: ES6 引入的新類型,表示獨(dú)一無(wú)二的、不可變的數(shù)據(jù)類型,主要用于對(duì)象的屬性鍵。
- BigInt: ES10 引入,用于表示任意大小的整數(shù)。
引用數(shù)據(jù)類型(Reference Data Types) 包括:
Object: 一種復(fù)雜數(shù)據(jù)結(jié)構(gòu),可以包含多個(gè)鍵值對(duì),包括但不限于普通對(duì)象、數(shù)組、函數(shù)等。
Array: 特殊類型的對(duì)象,用于存儲(chǔ)有序的元素集合。
Function: 在JavaScript中,函數(shù)也是對(duì)象,可以作為值傳遞,擁有方法和屬性
上面的數(shù)據(jù)類型如果說(shuō)有不熟悉的,那一般是Symbol和BigInt,下面我簡(jiǎn)要說(shuō)一下:
- Symbol:
最重要的特征就是唯一性,例如:
let a = Symbol(1) let b = Symbol(1) console.log(a === b) // false
用Symbol() 返回的東西,具有唯一性。
- BigInt:
Number
類型的安全整數(shù)范圍(-2^53 到 2^53),超出這個(gè)范圍的數(shù)進(jìn)行計(jì)算會(huì)出現(xiàn)精度丟失的問(wèn)題,算不準(zhǔn)確,于是就出現(xiàn)了BigInt.
特點(diǎn)
- 創(chuàng)建: BigInt 可以通過(guò)在整數(shù)末尾添加
n
來(lái)創(chuàng)建,例如123n
。你也可以使用BigInt()
函數(shù)將字符串轉(zhuǎn)換為 BigInt,如BigInt("123")
。 - 運(yùn)算: BigInt 和 Number 類型在進(jìn)行算術(shù)運(yùn)算時(shí)需要特別注意類型匹配。兩個(gè) BigInt 類型可以直接進(jìn)行加減乘除等運(yùn)算,但 BigInt 和 Number 直接運(yùn)算會(huì)導(dǎo)致錯(cuò)誤,需要先將 Number 轉(zhuǎn)換為 BigInt。
- 比較: BigInt 和 Number 之間可以進(jìn)行寬松的相等性比較(==),但嚴(yán)格相等性比較(===)會(huì)因?yàn)轭愋筒煌祷?false。嚴(yán)格比較時(shí),需要確保類型一致。
- 不支持: BigInt 不支持一元運(yùn)算符
++
和--
,也不適用于Math對(duì)象的方法,以及不能用于某些JavaScript原生對(duì)象的屬性,比如數(shù)組的長(zhǎng)度。 - 字符串轉(zhuǎn)換: BigInt 轉(zhuǎn)換為字符串時(shí),會(huì)保持其完整的數(shù)值,不會(huì)發(fā)生精度丟失。
示例
// 創(chuàng)建 BigInt const largeNum = 1234567890123456789012345678901234567890n; // 運(yùn)算 const anotherLargeNum = 9876543210987654321098765432109876543210n; const sum = largeNum + anotherLargeNum; // 比較 console.log(largeNum === BigInt('1234567890123456789012345678901234567890')); // true console.log(largeNum == 1234567890123456789012345678901234567890); // true, 松散比較 console.log(largeNum === 1234567890123456789012345678901234567890); // false, 嚴(yán)格比較類型不同 // 字符串轉(zhuǎn)換 console.log(largeNum.toString()); // '1234567890123456789012345678901234567890'
二、類型判斷時(shí)會(huì)產(chǎn)生的疑問(wèn)
1. typeof()類型判斷
console.log(typeof (null));//object console.log(typeof (undefined));//undefined console.log(typeof (true));//boolean console.log(typeof (20));//number console.log(typeof ("abc"));//string console.log(typeof (Symbol()));//symbol console.log(typeof (34n));//bigint console.log(typeof ([]));//object console.log(typeof ({}));//object console.log(typeof (function () { }));//function
我們看上面的代碼會(huì)產(chǎn)生兩個(gè)疑問(wèn):
為什么對(duì)null的類型判斷為object?
這個(gè)行為實(shí)際上是JavaScript設(shè)計(jì)初期的一個(gè)決策,后來(lái)成為了語(yǔ)言的一部分,被視為一個(gè)歷史遺留問(wèn)題。在JavaScript的最初設(shè)計(jì)中,類型信息是通過(guò)值的內(nèi)部表示來(lái)區(qū)分的,特別是通過(guò)值的頭部比特位。對(duì)于當(dāng)時(shí)的實(shí)現(xiàn)來(lái)說(shuō),null
的內(nèi)部二進(jìn)制表示是全零,這與對(duì)象類型在內(nèi)存中的某些標(biāo)記模式相吻合(判斷其二進(jìn)制前三位是否為0,是則為object
,否則為原始類型),尤其是當(dāng)引擎檢查值的頭部比特以快速區(qū)分基本類型和引用類型時(shí),全零可能被錯(cuò)誤地解釋為了一個(gè)空對(duì)象的標(biāo)記。至于后來(lái)為什么不改,則是因?yàn)榇罅康钠髽I(yè)已經(jīng)用JavaScript寫(xiě)了大量的項(xiàng)目,改動(dòng)后,全部項(xiàng)目都會(huì)報(bào)錯(cuò),基于這種考慮就沒(méi)動(dòng)。因此在以后判斷類型是否為object
時(shí),需要將null
排除。
為什么單獨(dú)function判斷類型為function,而非object?
在JavaScript中,函數(shù)(Function)是一種特殊的對(duì)象,這意味著它本質(zhì)上繼承了對(duì)象的特性,可以擁有屬性和方法。然而,出于對(duì)語(yǔ)言設(shè)計(jì)和實(shí)用性考慮,typeof
操作符特意將函數(shù)類型區(qū)分對(duì)待,當(dāng)應(yīng)用于函數(shù)時(shí),它返回的是"function"
而不是"object"
。
2. instanceof類型判斷
在JavaScript中,instanceof
是一個(gè)操作符,用于檢測(cè)構(gòu)造函數(shù)的prototype
屬性是否出現(xiàn)在某個(gè)實(shí)例對(duì)象的原型鏈上。它的使用方式與Java中的instanceof
相似,但概念上更符合JavaScript的原型繼承模型。其基本語(yǔ)法如下:
object instanceof Constructor
object
:需要檢查的對(duì)象。Constructor
:一個(gè)構(gòu)造函數(shù)或者函數(shù)對(duì)象。
如果object
是通過(guò)Constructor
或其任意父類(通過(guò)原型鏈)構(gòu)造的,那么instanceof
操作符返回true
;否則,返回false
。
例如:
function Animal() {} function Dog() {} Dog.prototype = new Animal(); let myDog = new Dog(); console.log(myDog instanceof Dog); // 輸出: true console.log(myDog instanceof Animal); // 輸出: true console.log(myDog instanceof Object); // 輸出: true,因?yàn)樗袑?duì)象都最終繼承自O(shè)bject
在這個(gè)例子中,myDog
對(duì)象是通過(guò)Dog
構(gòu)造函數(shù)創(chuàng)建的,而Dog
的原型鏈上包含了Animal
,因此myDog
既是Dog
的實(shí)例,也是Animal
的實(shí)例。同時(shí),由于JavaScript中所有對(duì)象都繼承自Object
,所以myDog
也是Object
的實(shí)例。
原始類型(如string
、number
、boolean
、null
、undefined
、symbol
、bigint
)不是對(duì)象,因此不能直接使用instanceof
來(lái)判斷這些類型。如果你嘗試對(duì)原始類型值使用instanceof
,它們會(huì)被臨時(shí)轉(zhuǎn)換為對(duì)應(yīng)的包裝對(duì)象(如new String()
、new Number()
、new Boolean()
),然后再進(jìn)行檢查,但這通常不是你想要的行為,并且對(duì)于null
和undefined
這樣的值,這樣做會(huì)直接導(dǎo)致錯(cuò)誤。
例如:
let str = "some text"; console.log(str instanceof String); // 可能意外地輸出: false,因?yàn)樽址皇荢tring對(duì)象的實(shí)例 // 實(shí)際上,"some text" 在進(jìn)行 instanceof 檢查前會(huì)被轉(zhuǎn)換為 String("some text"),但這是臨時(shí)的包裝對(duì)象,檢查后即被銷毀。 let num = 2; console.log(num instanceof Number); // 同樣可能輸出: false let bool = true; console.log(bool instanceof Boolean); // 輸出: false console.log(null instanceof Object); // 拋出 TypeError: null is not an object (evaluating 'null instanceof Object') console.log(undefined instanceof Object); // 拋出 TypeError: undefined is not an object (evaluating 'undefined instanceof Object')
面試題補(bǔ)充:請(qǐng)寫(xiě)出instanceof的判斷原理。
如果面試官出這種題,那對(duì)你算是非常溫柔了。
function myinstanceof(object, constructor) { // 當(dāng)對(duì)象不為null時(shí)進(jìn)入循環(huán),因?yàn)閚ull沒(méi)有__proto__ while (object !== null) { // 如果對(duì)象的原型等于構(gòu)造函數(shù)的prototype屬性,說(shuō)明該對(duì)象是構(gòu)造函數(shù)的實(shí)例 if (object.__proto__ === constructor.prototype) { return true; } else { // 如果當(dāng)前對(duì)象不是實(shí)例,繼續(xù)向上查找其原型鏈 object = object.__proto__; } } // 遍歷完原型鏈都沒(méi)有找到匹配,說(shuō)明不是該構(gòu)造函數(shù)的實(shí)例 return false; } console.log(myinstanceof({}, Object)); // true console.log(myinstanceof({}, Array)); // false
3. Object.prototype.toString.call( )
Object.prototype.toString.call()
是JavaScript中一個(gè)強(qiáng)大的方法,用于獲取任何值的類型信息。這個(gè)方法能夠返回一個(gè)表示該值的字符串,這個(gè)字符串格式通常為"[object Type]"
,其中Type
是JavaScript中的類型名稱。相比于前兩種判斷存在的瑕疵和不準(zhǔn)確,這種更為完美和準(zhǔn)確。
console.log(Object.prototype.toString.call({})); // "[object Object]" console.log(Object.prototype.toString.call([])); // "[object Array]" console.log(Object.prototype.toString.call(new Date)); // "[object Date]" console.log(Object.prototype.toString.call(null)); // "[object Null]" console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]" ...... ...... ......
在面試時(shí),很多大廠面試官會(huì)為難你,問(wèn):有什么辦法可以判斷類型?
你回答:Object.prototype.toString.call( )
,
他又問(wèn)你為什么這個(gè)方法可以判斷類型?
要回答好這個(gè)問(wèn)題,就需要我們對(duì)Object.prototype.toString.call( )
有著更為深入的理解:
這個(gè)方法要分兩部分理解:Object.prototype.toString
和call()
Object.prototype.toString
先來(lái)看官方文檔上寫(xiě)的Object.prototype.toString
翻譯:
調(diào)用tostring方法時(shí),會(huì)執(zhí)行以下步驟:
1.如果該值未定義,則返回“[object Undefined]”
2.如果this
值為null
,則返回“[object Null]”
3.讓o
成為調(diào)用To Obiect
的結(jié)果,將this
值作為參數(shù)傳遞。(將 o
作為 To Object(this)
的執(zhí)行結(jié)果)
4.定義class
為o
的內(nèi)部屬性 [[Class]]
的值
5.返回String
值,該值是由三個(gè)String“[object”
、class
和 “]”
連接起來(lái)的結(jié)果。
你可以將以上步驟理解為以下步驟:
1.檢查值是否為undefined
:如果調(diào)用toString
的方法的對(duì)象是undefined
,則返回"[object Undefined]"
。
2.檢查值是否為null
:如果該值是null
,則返回"[object Null]"
。
3.轉(zhuǎn)換為對(duì)象(To Object
操作):對(duì)于非null
和非undefined
的值,首先通過(guò)抽象操作To Object
將其轉(zhuǎn)換為對(duì)象(如果還不是對(duì)象)。這意味著原始值(如數(shù)字、字符串等)會(huì)先轉(zhuǎn)換為它們的包裝對(duì)象,然后繼續(xù)后續(xù)步驟。
4.獲取內(nèi)部屬性[[Class]]
:獲取轉(zhuǎn)換后的對(duì)象的內(nèi)部屬性[[Class]]
的值。這個(gè)屬性由JavaScript引擎維護(hù),代表了對(duì)象的類型信息,比如"Array"
、"Date"
、"Object"
等。
5.構(gòu)造并返回結(jié)果字符串:最后,將字符串"[object "
, class
的值,以及"]"
拼接起來(lái),形成并返回最終的類型字符串,如"[object Array]"
、"[object Date]"
等。
然而用Object.prototype.toString()
遠(yuǎn)遠(yuǎn)不足以有效的判斷類型,盡管它很強(qiáng)大:
Object.prototype.toString()
判斷類型時(shí),通常不會(huì)按照預(yù)期工作,尤其是當(dāng)直接在原始值(如字符串、數(shù)字、布爾)上嘗試時(shí),因?yàn)檫@樣調(diào)用的this
并沒(méi)有綁定到你想要檢查的對(duì)象上。
其原因在于第三個(gè)步驟To Obiect
,官方文檔對(duì)此如下描述:
它會(huì)new
一個(gè)對(duì)象,而不是單純的字面量,此時(shí)其this
指向發(fā)生改變,其內(nèi)部屬性[[class]]
為指向?qū)ο蟮膬?nèi)部屬性object
。
因此,需要call()的幫助改變其this指向:
- call
不了解call的,我簡(jiǎn)單舉個(gè)例子來(lái)說(shuō)明一下效果:
var object = { a: 11 } function foo() { console.log(this.a) } foo.call(obj) // 11
想通過(guò)func函數(shù)輸出1,可以通過(guò)call方法將func里的this指向object。
我們可以嘗試模擬call
方法的行為,寫(xiě)如下代碼:
var object = { a: 11 }; function foo() { console.log(this.a); } Function.prototype.mycall = function(context) { // 檢查調(diào)用mycall的是否為一個(gè)函數(shù) if (typeof this !== 'function') { throw new TypeError(this + ' is not a function'); } // 使用Symbol來(lái)避免屬性名沖突 const fn = Symbol('key'); // 將當(dāng)前函數(shù)(this指向的func)賦值給context的一個(gè)唯一屬性 context[fn] = this; // 調(diào)用這個(gè)新添加的函數(shù),此時(shí)this會(huì)被隱式綁定到context上 context[fn](); // 刪除臨時(shí)添加的屬性,以清理環(huán)境 delete context[fn]; }; foo.mycall(object); // 輸出: 11
核心原理可以概括為: call
方法通過(guò)在指定的context
對(duì)象上臨時(shí)引用并調(diào)用目標(biāo)函數(shù),實(shí)現(xiàn)了對(duì)該函數(shù)內(nèi)部this
的隱式綁定,從而使得函數(shù)能夠在預(yù)期的上下文中執(zhí)行。
具體來(lái)說(shuō):
- 臨時(shí)綁定:它本質(zhì)上是在
context
對(duì)象上創(chuàng)建一個(gè)屬性(通常使用一個(gè)不易沖突的屬性名,如使用Symbol),并將目標(biāo)函數(shù)賦值給這個(gè)屬性。(Symbol作用在于,防止別人調(diào)用你寫(xiě)的方法時(shí),用同名的變量名) - 調(diào)用函數(shù):接著,通過(guò)
context
上的這個(gè)屬性間接調(diào)用目標(biāo)函數(shù)。由于是通過(guò)對(duì)象屬性的方式來(lái)調(diào)用的,JavaScript的函數(shù)調(diào)用規(guī)則決定了此時(shí)函數(shù)內(nèi)的this
將綁定到該對(duì)象(即context
)上。 - 清理:為了不污染
context
對(duì)象,調(diào)用結(jié)束后通常還會(huì)刪除之前添加的臨時(shí)屬性,即清除本來(lái)就不存在context
里的屬性。
看完這兩部分的解釋,再來(lái)做一個(gè)總結(jié):
使用Object.prototype.toString.call()
時(shí),當(dāng)參數(shù)為Boolean
,Number
和String
類型,call()
先將這個(gè)原始值轉(zhuǎn)換為其對(duì)應(yīng)的包裝對(duì)象,即new String('11')
,然后再調(diào)用Object.prototype.toString
方法,當(dāng)執(zhí)行到第三步to object
時(shí),發(fā)現(xiàn)參數(shù)為對(duì)象,則將對(duì)象賦給變量o。在這個(gè)過(guò)程中this指向因?yàn)?strong>call的糾正作用沒(méi)有發(fā)生改變,因此,其內(nèi)部屬性[[class]]
沒(méi)有發(fā)生改變。
加上了call,你可以理解為以下情況:
Object.prototype.toString.call('11'); // 輸出: "[object String]" Object.prototype.toString.call(new String('11')); // 輸出同樣為: "[object String]"
寫(xiě)出完整步驟: 當(dāng)執(zhí)行Object.prototype.toString.call('11')
時(shí),其內(nèi)部過(guò)程大致如下:
- 字符串字面量
'11'
作為call
的第一個(gè)參數(shù),使得toString
方法內(nèi)部的this
指向了一個(gè)臨時(shí)創(chuàng)建的String
對(duì)象(即new String('1')
)。 - 該方法檢查
this
不是null
或undefined
,繼續(xù)執(zhí)行。 - 將這個(gè)臨時(shí)的字符串對(duì)象視為操作對(duì)象
O
。 - 從
O
中獲取其內(nèi)部屬性[[Class]]
,得到值"String"
。 - 組合并返回字符串
"[object String]"
,表示這是一個(gè)字符串類型的對(duì)象。
4.Array.isArray(x)
Array.isArray(x)
是JavaScript的一個(gè)內(nèi)建函數(shù),用于檢測(cè)x
是否為一個(gè)數(shù)組。這個(gè)方法提供了最直接和可靠的方式來(lái)判斷一個(gè)變量是否是數(shù)組類型,相比使用instanceof
或typeof
等方法更準(zhǔn)確,因?yàn)樗粫?huì)受到不同全局執(zhí)行環(huán)境(如iframe、Web Workers)中Array構(gòu)造函數(shù)不同的影響。
使用示例:
let arr = [1, 2, 3]; let notArr = "I am not an array"; console.log(Array.isArray(arr)); // 輸出: true console.log(Array.isArray(notArr)); // 輸出: false
這個(gè)函數(shù)非常有用,尤其是在處理可能是多種類型輸入的動(dòng)態(tài)數(shù)據(jù)時(shí),能夠確保你正確地識(shí)別并處理數(shù)組類型的數(shù)據(jù)。
三、結(jié)語(yǔ):
在JavaScript的世界里,準(zhǔn)確無(wú)誤地判斷數(shù)據(jù)類型是編寫(xiě)健壯、可維護(hù)代碼的基礎(chǔ)。本文從基礎(chǔ)出發(fā),系統(tǒng)梳理了JavaScript的各大數(shù)據(jù)類型,重點(diǎn)解析了在類型判斷時(shí)常見(jiàn)的疑惑與誤區(qū),尤其深入探討了typeof
、instanceof
以及Object.prototype.toString.call()
這三種類型判斷方法的原理與實(shí)踐,最后還提到了Array.isArray()
這一專門用于數(shù)組類型判斷的便捷工具。
通過(guò)本篇內(nèi)容的學(xué)習(xí),你不僅掌握了每種判斷方法的適用場(chǎng)景與限制,還理解了如何利用Object.prototype.toString.call()
這一終極武器來(lái)實(shí)現(xiàn)精確無(wú)誤的類型識(shí)別。記住,每種方法都有其獨(dú)特的價(jià)值和潛在的陷阱,合理選擇才能在實(shí)戰(zhàn)中游刃有余。
以上就是深入探究JavaScript的類型判斷(從基礎(chǔ)到精通)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript類型判斷的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Leaflet?數(shù)據(jù)可視化實(shí)現(xiàn)地圖下鉆示例詳解
這篇文章主要為大家介紹了Leaflet數(shù)據(jù)可視化實(shí)現(xiàn)地圖下鉆示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01JavaScript中Hoisting詳解 (變量提升與函數(shù)聲明提升)
函數(shù)聲明和變量聲明總是被JavaScript解釋器隱式地提升(hoist)到包含他們的作用域的最頂端。下面這篇文章主要給大家介紹了關(guān)于JavaScript中Hoisting(變量提升與函數(shù)聲明提升)的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-08-08JavaScript數(shù)據(jù)結(jié)構(gòu)之雙向鏈表
這篇文章主要為大家詳細(xì)介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)之雙向鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03javascript:void(0)點(diǎn)擊登錄沒(méi)反應(yīng)怎么解決
這篇文章給大家介紹javascript:void(0)點(diǎn)擊登錄沒(méi)反應(yīng)怎么解決,解決辦法是巧用批處理解決IE不支持JavaScript等問(wèn)題,需要的朋友參考下2015-11-11使用canvas繪圖音樂(lè)頻譜示例及技術(shù)分析
這篇文章主要為大家介紹了使用canvas實(shí)現(xiàn)音樂(lè)頻譜示例及技術(shù)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12JS實(shí)現(xiàn)的省份級(jí)聯(lián)實(shí)例代碼
這篇文章主要介紹了js下省份級(jí)聯(lián)效果,需要的朋友可以參考一下2013-06-06微信小程序?qū)崿F(xiàn)五星評(píng)價(jià)功能
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)五星評(píng)價(jià)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08