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

