JS必備技能之數(shù)據(jù)類型判斷與底層原理深入解析
一、簡介
? 精準判斷數(shù)據(jù)類型是每一位前端開發(fā)者的必備技能,但 JS 因存在基礎類型與引用類型的區(qū)分以及歷史遺留問題等,單一判斷方式往往無法覆蓋所有場景。本文梳理并解析了 5 類常用的數(shù)據(jù)類型判斷方法,包括:基礎類型常用的 typeof、對象類型專屬的 instanceof 、全類型通用的 Object.prototype.toString.call()、基于構造函數(shù)引用的 constructor,以及針對數(shù)組、NaN、整數(shù)等場景的特定判斷方案。
基礎類型:
String、Number、Boolean、Null、Undefined、Symbol、bigint(ES2020新增)。
這些類型的數(shù)據(jù)占據(jù)的空間較小且大小是固定的,因此數(shù)據(jù)直接存儲在棧(stack)內存中,方便頻繁調用數(shù)據(jù)。
引用類型:
? Object(包含Array、Function、Date、RegExp、Map、Set等等)。
? 該類型的數(shù)據(jù)占據(jù)的空間較大且大小不固定,是將數(shù)據(jù)存儲在堆(heap)內存中,并在棧(stack)內存中存儲一個引用指針,該指針指向當前數(shù)據(jù)在堆中的實際空間地址。
二、具體方案
1、typeof
? typeof 運算符常用于獲取基礎數(shù)據(jù)的類型,可以獲取到一個表示數(shù)據(jù)的類型的字符串,類型字符串有:number、string、boolean、object、function、undefined、symbol、bigint。
? typeof 運算符在遇到除 null 之外基礎類型的數(shù)據(jù),都能正常獲取其類型字符串(number、string等),而遇到 null 會返回 object ,這是由于歷史遺留問題。但遇到對象類型數(shù)據(jù),其判斷結果具有局限性,遇到函數(shù)則會返回 function ,而遇到其他對象(如對象、數(shù)組、日期等)都只會返回 object,因此無法區(qū)分具體對象類型。
基本語法:
typeof 數(shù)據(jù)
判斷原理:
? JS中的數(shù)據(jù),在底層中都是以二進制的形式存儲的,并且會通過低位的 “類型標簽”(tag) 來區(qū)分不同數(shù)據(jù)類型,不同的JS引擎具體實現(xiàn)不同,比如V8引擎中:二進制000結尾-表示對象類型、二進制1結尾-表示整數(shù)類型等等。typeof 的原理就是通過底層標簽來判斷數(shù)據(jù)的類型,而特殊的null,其所有二進制位都是 0 ,所以引擎會將以 000 結尾的標簽將其誤判為 object。
使用示例:
// 數(shù)字類型
typeof 37 === 'number';
typeof 3.14 === 'number';
typeof Math.LN2 === 'number';
typeof Infinity === 'number';
typeof NaN === 'number'; // 盡管NaN是非數(shù)字的意思
typeof Number(1) === 'number'; // 但不建議使用!
// bigInt
typeof 42n === 'bigint';
// 字符串類型
typeof "" === 'string';
typeof "bla" === 'string';
typeof (typeof 1) === 'string'; // typeof返回的是字符串
typeof String("abc") === 'string'; // 但不建議使用!
// 布爾類型
typeof true === 'boolean';
typeof false === 'boolean';
typeof Boolean(true) === 'boolean'; // 但不建議使用!
// Symbol類型
typeof Symbol() === 'symbol';
typeof Symbol('foo') === 'symbol';
typeof Symbol.iterator === 'symbol';
// Undefined
typeof undefined === 'undefined';
var a // 聲明但未賦值
typeof a === 'undefined';
typeof b === 'undefined'; // 未聲明未賦值
// 對象類型
typeof {a:1} === 'object';
typeof [1, 2, 4] === 'object'; // 數(shù)組會被判斷為對象
typeof new Date() === 'object';
typeof null === 'object'; // null 也會被判斷為對象
// 函數(shù)
typeof function(){} === 'function';
typeof Math.sin === 'function';
2、instanceof
? instanceof 運算符常用于判斷對象數(shù)據(jù)的具體數(shù)據(jù)類型,可以判斷一個對象數(shù)據(jù)是否為某個構造函數(shù)的實例,返回一個布爾值。
? instanceof 運算符不適用于基礎數(shù)據(jù)類型(包括null),但適用于基礎類型的包裝對象。并且如果判斷數(shù)組、函數(shù)等具體對象數(shù)據(jù)是否為 Object 類型,結果也會是true,因為 Object 類型屬于最外層的大類型。因此想要判斷具體的對象類型,需要精準的判斷,不能用排除法。
? 由于原型鏈是可以被修改的,因此判斷結果并非絕對可靠。
基本語法:
對象 instanceof 構造函數(shù)
判斷原理:
? JS中對象數(shù)據(jù)存在原型鏈,instanceof 就是通過沿著左邊對象的原型鏈向上查找,如果在左邊對象的原型鏈上存在右邊構造函數(shù)的 prototype ,則說明左邊對象是右邊構造函數(shù)的實例,也就是說左邊對象屬于右邊構造函數(shù)的類型,返回 true,否則返回 false。
? 因此該方案只適用于同一個全局環(huán)境下的判斷,因為在不同的全局環(huán)境(如iframe)中,構造函數(shù)的 prototype 指向的是不同的對象,會導致判斷錯誤。
使用示例:
// 數(shù)組
var a = [1,2,3]
console.log(a instanceof Array) // true
console.log(a instanceof Object) // true,兩者都成立
var a2 = new Array
console.log(a2 instanceof Array) // true
console.log(a2 instanceof Object) // true
// 函數(shù)(包括箭頭函數(shù))
var b = function() {}
console.log(b instanceof Function) // true
console.log((()=>{}) instanceof Function) // true
console.log(b instanceof Object) // true
// 對象
var c = {}
console.log(c instanceof Object) // true
console.log(Date instanceof Object) // true
// 內置對象
var str = new String()
str instanceof String // true
var myDate = new Date()
myDate instanceof Date; // true
myDate instanceof Object; // true
// 基礎數(shù)據(jù)的包裝類
const numObj = new Number(123);
numObj instanceof Number; // true(包裝對象可被判斷)
123 instanceof Number; // false(原始值不可被判斷)
3、Object.prototype.toString.call()
? Object.prototype.toString.call() 方法可用于判斷所有數(shù)據(jù)的具體類型,可以獲取到一個表示數(shù)據(jù)具體類型的標準字符串,返回格式為: [object Xxxx],其中 Xxxx 就是數(shù)據(jù)的具體類型,無論是基本數(shù)據(jù)類型還是對象數(shù)據(jù)類型,都能精準的區(qū)分。
基本語法:
Object.prototype.toString.call(數(shù)據(jù));
判斷原理:
? 每個JS內置對象都有一個內部屬性 [[Class]](抽象概念,無法直接訪問),其值為該對象的類型標識(如 Array、Date、Number、String 等)。當調用 Object 原型上的 toString() 方法,并通過 call() 方法改變this的指向到目標數(shù)據(jù)時,對于對象類型數(shù)據(jù),該方法會直接讀取目標數(shù)據(jù)的 [[Class]] 屬性,并返回對應類型字符串 [object [[Class]]];對于基本類型數(shù)據(jù), call() 方法會先自動將其轉換為對應的包裝對象,再讀取其 [[Class]] 屬性,最后返回對應類型字符串 [object [[Class]]]。
使用示例:
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call('abc'); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call([1, 2]); // "[object Array]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(/abc/); // "[object RegExp]"
Object.prototype.toString.call(function(){}); // "[object Function]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call(Symbol()); // "[object Symbol]"
Object.prototype.toString.call(123n); // "[object BigInt]"
Object.prototype.toString.call(new Map()); // "[object Map]"
Object.prototype.toString.call(new Set()); // "[object Set]"
Object.prototype.toString.call(new WeakMap()); // "[object WeakMap]"
4、constructor
? constructor 常用于判斷對象的具體類型,其是對象的一個屬性,它指向創(chuàng)建該對象的構造函數(shù),通過與特定的構造函數(shù)進行比較,區(qū)分對象的具體類型。
? 對于除 null、undefined 之外基礎類型數(shù)據(jù),在其調用 constructor 屬性時,JS會自動進行“裝箱”操作,將其臨時變?yōu)閷陌b對象,從而擁有 constructor 屬性;對于null、undefined,它們沒有對應的包裝對象,因此無法調用 constructor。
? 由于 constructor 屬性是可以被修改的,因此判斷結果并非絕對可靠。
基本語法:
數(shù)據(jù).constructor === 構造函數(shù)
判斷原理:
? JS中每個對象在創(chuàng)建時都會關聯(lián)一個構造函數(shù),constructor 屬性就是對這個構造函數(shù)的引用。例如,數(shù)組是由 Array 構造函數(shù)創(chuàng)建的,所以數(shù)組的 constructor 屬性指向 Array;日期對象由 Date 構造函數(shù)創(chuàng)建,其 constructor 屬性指向 Date。因此,通過比較對象的 constructor 與對應的構造函數(shù),就能判斷出對象的具體類型。
使用示例:
// 基礎數(shù)據(jù)類型(除 null、undefined 外,通過包裝對象實現(xiàn))
(123).constructor === Number; // true
// 等價于
Number(123).constructor === Number;
'abc'.constructor === String; // true
// 等價于
String('abc').constructor === String;
true.constructor === Boolean; // true
// 等價于
// 引用數(shù)據(jù)類型
[1, 2].constructor === Array; // true(可識別數(shù)組)
new Date().constructor === Date; // true(可識別日期對象)
({}).constructor === Object; // true
5、特定類型判斷
① Array.isArray()
? 判斷數(shù)據(jù)是否為數(shù)組類型。
var arr = [] Array.isArray(arr); // true
② ===
? 準確判斷 null 和 undefined。
let a = null; a === null; // true let b = undefined; b === undefined; // true a === b; // false
③ Number.isNaN()
? 判斷數(shù)據(jù)是否為NaN。該方法不會將參數(shù)強制轉換為數(shù)字類型,會直接判斷傳入的參數(shù)是否是 NaN,而全局的 isNaN() 方法會強制轉換參數(shù)為數(shù)字,導致非NaN值被誤判,因此更推薦使用 Number.isNaN()。
let n = NaN; Number.isNaN(n); // true
④ Number.isInteger()
? 判斷數(shù)據(jù)是否為整數(shù)。
Number.isInteger(1); // true Number.isInteger(1.2); // false Number.isInteger(1.00); // true
三、 總結
? 沒有萬能的方法,只有最適合當前場景的方法。
| 判斷方法 | 適用場景 | 關鍵局限 | 精準度 |
|---|---|---|---|
| typeof | 基礎類型(除 null)、函數(shù)快速判斷 | null 誤判為 object,無法區(qū)分數(shù)組 / 日期等對象 | 基礎類型高,引用類型低 |
| instanceof | 引用類型(數(shù)組、日期等)的實例判斷 | 不支持原始基礎類型,原型鏈修改會導致誤判 | 引用類型較高(需注意全局環(huán)境) |
| Object.prototype.toString.call() | 所有類型(基礎 + 引用)精準判斷 | 語法相對繁瑣,需處理返回的標準格式字符串 | 最高(全場景通用) |
| constructor | 基礎類型(除 null/undefined)、引用類型判斷 | constructor 可被修改,null/undefined 無該屬性 | 較高(需警惕屬性篡改) |
| 特定判斷 | 單一類型(數(shù)組、NaN、整數(shù)等)精準校驗 | 適用范圍窄,僅針對特定場景 | 極高(場景專屬) |
總結
到此這篇關于JS必備技能之數(shù)據(jù)類型判斷與底層原理的文章就介紹到這了,更多相關JS數(shù)據(jù)類型判斷與底層原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解析ScrollPic在ie8下只滾動一遍,然后變?yōu)榭瞻?ie6,ie7,chrome,firefox正常
解析ScrollPic在ie8下只滾動一遍,然后變?yōu)榭瞻?ie6,ie7,chrome,firefox都正常)2013-06-06
在一個瀏覽器里呈現(xiàn)所有瀏覽器測試結果的前端測試工具的思路
對前端工程師來說,跨瀏覽器的兼容性問題一直是最頭疼的,測試一個小小的東西,就要打開N個瀏覽器,然后比較來比較去,記錄個瀏覽器的數(shù)據(jù),比較不同,實在是麻煩.2010-03-03
對javascript的一點點認識總結《javascript高級程序設計》讀書筆記
Javascript專為與網(wǎng)頁交互而設計的腳本語言,由下列三個部門構造2011-11-11
JavaScript用二分法查找數(shù)據(jù)的實例代碼
本篇文章主要介紹了JavaScript用二分法查找數(shù)據(jù)的實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06

