深入探究JavaScript的類型判斷(從基礎到精通)
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: 一種復雜數(shù)據(jù)結構,可以包含多個鍵值對,包括但不限于普通對象、數(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ù)進行計算會出現(xiàn)精度丟失的問題,算不準確,于是就出現(xiàn)了BigInt.
特點
- 創(chuàng)建: BigInt 可以通過在整數(shù)末尾添加
n來創(chuàng)建,例如123n。你也可以使用BigInt()函數(shù)將字符串轉換為 BigInt,如BigInt("123")。 - 運算: BigInt 和 Number 類型在進行算術運算時需要特別注意類型匹配。兩個 BigInt 類型可以直接進行加減乘除等運算,但 BigInt 和 Number 直接運算會導致錯誤,需要先將 Number 轉換為 BigInt。
- 比較: BigInt 和 Number 之間可以進行寬松的相等性比較(==),但嚴格相等性比較(===)會因為類型不同而返回 false。嚴格比較時,需要確保類型一致。
- 不支持: BigInt 不支持一元運算符
++和--,也不適用于Math對象的方法,以及不能用于某些JavaScript原生對象的屬性,比如數(shù)組的長度。 - 字符串轉換: BigInt 轉換為字符串時,會保持其完整的數(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, 嚴格比較類型不同
// 字符串轉換
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設計初期的一個決策,后來成為了語言的一部分,被視為一個歷史遺留問題。在JavaScript的最初設計中,類型信息是通過值的內(nèi)部表示來區(qū)分的,特別是通過值的頭部比特位。對于當時的實現(xiàn)來說,null的內(nèi)部二進制表示是全零,這與對象類型在內(nèi)存中的某些標記模式相吻合(判斷其二進制前三位是否為0,是則為object,否則為原始類型),尤其是當引擎檢查值的頭部比特以快速區(qū)分基本類型和引用類型時,全零可能被錯誤地解釋為了一個空對象的標記。至于后來為什么不改,則是因為大量的企業(yè)已經(jīng)用JavaScript寫了大量的項目,改動后,全部項目都會報錯,基于這種考慮就沒動。因此在以后判斷類型是否為object時,需要將null排除。
為什么單獨function判斷類型為function,而非object?
在JavaScript中,函數(shù)(Function)是一種特殊的對象,這意味著它本質上繼承了對象的特性,可以擁有屬性和方法。然而,出于對語言設計和實用性考慮,typeof操作符特意將函數(shù)類型區(qū)分對待,當應用于函數(shù)時,它返回的是"function"而不是"object"。
2. instanceof類型判斷
在JavaScript中,instanceof 是一個操作符,用于檢測構造函數(shù)的prototype屬性是否出現(xiàn)在某個實例對象的原型鏈上。它的使用方式與Java中的instanceof相似,但概念上更符合JavaScript的原型繼承模型。其基本語法如下:
object instanceof Constructor
object:需要檢查的對象。Constructor:一個構造函數(shù)或者函數(shù)對象。
如果object是通過Constructor或其任意父類(通過原型鏈)構造的,那么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,因為所有對象都最終繼承自Object
在這個例子中,myDog對象是通過Dog構造函數(shù)創(chuàng)建的,而Dog的原型鏈上包含了Animal,因此myDog既是Dog的實例,也是Animal的實例。同時,由于JavaScript中所有對象都繼承自Object,所以myDog也是Object的實例。
原始類型(如string、number、boolean、null、undefined、symbol、bigint)不是對象,因此不能直接使用instanceof來判斷這些類型。如果你嘗試對原始類型值使用instanceof,它們會被臨時轉換為對應的包裝對象(如new String()、new Number()、new Boolean()),然后再進行檢查,但這通常不是你想要的行為,并且對于null和undefined這樣的值,這樣做會直接導致錯誤。
例如:
let str = "some text";
console.log(str instanceof String); // 可能意外地輸出: false,因為字符串不是String對象的實例
// 實際上,"some text" 在進行 instanceof 檢查前會被轉換為 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) {
// 當對象不為null時進入循環(huán),因為null沒有__proto__
while (object !== null) {
// 如果對象的原型等于構造函數(shù)的prototype屬性,說明該對象是構造函數(shù)的實例
if (object.__proto__ === constructor.prototype) {
return true;
} else {
// 如果當前對象不是實例,繼續(xù)向上查找其原型鏈
object = object.__proto__;
}
}
// 遍歷完原型鏈都沒有找到匹配,說明不是該構造函數(shù)的實例
return false;
}
console.log(myinstanceof({}, Object)); // true
console.log(myinstanceof({}, Array)); // false
3. Object.prototype.toString.call( )
Object.prototype.toString.call() 是JavaScript中一個強大的方法,用于獲取任何值的類型信息。這個方法能夠返回一個表示該值的字符串,這個字符串格式通常為"[object Type]",其中Type是JavaScript中的類型名稱。相比于前兩種判斷存在的瑕疵和不準確,這種更為完美和準確。
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

翻譯:
調用tostring方法時,會執(zhí)行以下步驟:
1.如果該值未定義,則返回“[object Undefined]”
2.如果this值為null,則返回“[object Null]”
3.讓o成為調用To Obiect的結果,將this值作為參數(shù)傳遞。(將 o 作為 To Object(this) 的執(zhí)行結果)
4.定義class為o的內(nèi)部屬性 [[Class]] 的值
5.返回String值,該值是由三個String“[object”、class 和 “]” 連接起來的結果。
你可以將以上步驟理解為以下步驟:
1.檢查值是否為undefined:如果調用toString的方法的對象是undefined,則返回"[object Undefined]"。
2.檢查值是否為null:如果該值是null,則返回"[object Null]"。
3.轉換為對象(To Object操作):對于非null和非undefined的值,首先通過抽象操作To Object將其轉換為對象(如果還不是對象)。這意味著原始值(如數(shù)字、字符串等)會先轉換為它們的包裝對象,然后繼續(xù)后續(xù)步驟。
4.獲取內(nèi)部屬性[[Class]]:獲取轉換后的對象的內(nèi)部屬性[[Class]]的值。這個屬性由JavaScript引擎維護,代表了對象的類型信息,比如"Array"、"Date"、"Object"等。
5.構造并返回結果字符串:最后,將字符串"[object ", class的值,以及"]"拼接起來,形成并返回最終的類型字符串,如"[object Array]"、"[object Date]"等。
然而用Object.prototype.toString()遠遠不足以有效的判斷類型,盡管它很強大:

Object.prototype.toString()判斷類型時,通常不會按照預期工作,尤其是當直接在原始值(如字符串、數(shù)字、布爾)上嘗試時,因為這樣調用的this并沒有綁定到你想要檢查的對象上。
其原因在于第三個步驟To Obiect,官方文檔對此如下描述:

它會new一個對象,而不是單純的字面量,此時其this指向發(fā)生改變,其內(nèi)部屬性[[class]]為指向對象的內(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) {
// 檢查調用mycall的是否為一個函數(shù)
if (typeof this !== 'function') {
throw new TypeError(this + ' is not a function');
}
// 使用Symbol來避免屬性名沖突
const fn = Symbol('key');
// 將當前函數(shù)(this指向的func)賦值給context的一個唯一屬性
context[fn] = this;
// 調用這個新添加的函數(shù),此時this會被隱式綁定到context上
context[fn]();
// 刪除臨時添加的屬性,以清理環(huán)境
delete context[fn];
};
foo.mycall(object); // 輸出: 11
核心原理可以概括為: call方法通過在指定的context對象上臨時引用并調用目標函數(shù),實現(xiàn)了對該函數(shù)內(nèi)部this的隱式綁定,從而使得函數(shù)能夠在預期的上下文中執(zhí)行。
具體來說:
- 臨時綁定:它本質上是在
context對象上創(chuàng)建一個屬性(通常使用一個不易沖突的屬性名,如使用Symbol),并將目標函數(shù)賦值給這個屬性。(Symbol作用在于,防止別人調用你寫的方法時,用同名的變量名) - 調用函數(shù):接著,通過
context上的這個屬性間接調用目標函數(shù)。由于是通過對象屬性的方式來調用的,JavaScript的函數(shù)調用規(guī)則決定了此時函數(shù)內(nèi)的this將綁定到該對象(即context)上。 - 清理:為了不污染
context對象,調用結束后通常還會刪除之前添加的臨時屬性,即清除本來就不存在context里的屬性。
看完這兩部分的解釋,再來做一個總結:
使用Object.prototype.toString.call()時,當參數(shù)為Boolean,Number和String類型,call()先將這個原始值轉換為其對應的包裝對象,即new String('11'),然后再調用Object.prototype.toString方法,當執(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]"
寫出完整步驟: 當執(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í)行環(huán)境(如iframe、Web Workers)中Array構造函數(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ù)。
三、結語:
在JavaScript的世界里,準確無誤地判斷數(shù)據(jù)類型是編寫健壯、可維護代碼的基礎。本文從基礎出發(fā),系統(tǒng)梳理了JavaScript的各大數(shù)據(jù)類型,重點解析了在類型判斷時常見的疑惑與誤區(qū),尤其深入探討了typeof、instanceof以及Object.prototype.toString.call()這三種類型判斷方法的原理與實踐,最后還提到了Array.isArray()這一專門用于數(shù)組類型判斷的便捷工具。
通過本篇內(nèi)容的學習,你不僅掌握了每種判斷方法的適用場景與限制,還理解了如何利用Object.prototype.toString.call()這一終極武器來實現(xiàn)精確無誤的類型識別。記住,每種方法都有其獨特的價值和潛在的陷阱,合理選擇才能在實戰(zhàn)中游刃有余。
以上就是深入探究JavaScript的類型判斷(從基礎到精通)的詳細內(nèi)容,更多關于JavaScript類型判斷的資料請關注腳本之家其它相關文章!
相關文章
Leaflet?數(shù)據(jù)可視化實現(xiàn)地圖下鉆示例詳解
這篇文章主要為大家介紹了Leaflet數(shù)據(jù)可視化實現(xiàn)地圖下鉆示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01
JavaScript中Hoisting詳解 (變量提升與函數(shù)聲明提升)
函數(shù)聲明和變量聲明總是被JavaScript解釋器隱式地提升(hoist)到包含他們的作用域的最頂端。下面這篇文章主要給大家介紹了關于JavaScript中Hoisting(變量提升與函數(shù)聲明提升)的相關資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-08-08

