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