JavaScript獲取數(shù)據(jù)類型的方法詳解
說明
本文所介紹的所有知識(shí)點(diǎn)、代碼示例以及提供的解決方案,均不考慮 IE 瀏覽器,僅支持最新版本的 Chrome、Firefox、Edge 和 Safari 瀏覽器。
概述
前端開發(fā)過程中一個(gè)常見的功能是:檢測(cè)某個(gè)數(shù)據(jù)屬于什么類型,是字符串、數(shù)字、數(shù)組、還是對(duì)象等等。比如,我們定義了一個(gè)函數(shù),并且支持傳參,往往就需要對(duì)傳入的參數(shù)進(jìn)行數(shù)據(jù)類型檢測(cè),然后根據(jù)檢測(cè)結(jié)果進(jìn)行相應(yīng)的處理,這時(shí)我們就必須知道如何準(zhǔn)確的獲取數(shù)據(jù)的類型。在構(gòu)思解決方案之前,我們首先需要回顧一下基礎(chǔ)知識(shí),那就是在 JavaScript 中到底有幾種數(shù)據(jù)類型?
數(shù)據(jù)類型種類
這里所講的數(shù)據(jù)類型指的是 JavaScript 語(yǔ)言層面的數(shù)據(jù)類型,截至目前,共有 8 種類型,可分為【基本數(shù)據(jù)類型】和【引用數(shù)據(jù)類型】:
基本數(shù)據(jù)類型
- 字符串(
String) - 數(shù)字(
Number) - 布爾值 (
Boolean) nullundefinedSymbolBigInt
引用數(shù)據(jù)類型
- 對(duì)象(
Object,Array等等 )
區(qū)別
上面提到的【基本數(shù)據(jù)類型】和【引用數(shù)據(jù)類型】有什么區(qū)別呢?
基本數(shù)據(jù)類型的值是保存在 “棧” 內(nèi)存中的,它是可以直接訪問的,所有的讀寫操作都是直接作用于數(shù)據(jù)本身,中間沒有任何 “轉(zhuǎn)接” 行為。
引用數(shù)據(jù)類型的值是保存在 “堆” 內(nèi)存中的,在 JavaScript 中是不允許直接訪問堆內(nèi)存中的數(shù)據(jù)的,要想訪問就需要拿到它在堆內(nèi)存中的地址,然后通過這個(gè)地址進(jìn)行讀寫操作。
舉個(gè)例子:張三要跟李四溝通事情,基本數(shù)據(jù)類型就相當(dāng)于,張三直接跟李四本人交流。而引用數(shù)據(jù)類型則相當(dāng)于張三要跟 “代理人” 溝通,再由這個(gè) “代理人” 把張三的需求轉(zhuǎn)述給李四,李四如有反饋,也必須通過 “代理人” 轉(zhuǎn)告給張三,張三和李四由始至終都不能直接溝通。
檢測(cè)方法
typeof 運(yùn)算符
這是最簡(jiǎn)單也是最常用的數(shù)據(jù)類型檢測(cè)方法,但同時(shí)它也不太 “靠譜”,為什么這樣說呢?可以先看看下面的代碼示例:
console.log( typeof "data" ); // string
console.log( typeof 123456 ); // number
console.log( typeof true ); // boolean
console.log( typeof function () {} ); // function
console.log( typeof Symbol() ); // symbol
console.log( typeof 100n ); // bigint
console.log( typeof undefined ); // undefined
console.log( "===================================" );
console.log( typeof null ); // object
console.log( typeof { a: "a" } ); // object
console.log( typeof [ 1, 2, 3 ] ); // object
可以看到,對(duì)于前七種數(shù)據(jù),能檢測(cè)出相應(yīng)的類型,而后三種卻一律返回 object。前面曾提到,Array 和 Object 都屬于引用數(shù)據(jù)類型,而 null 被認(rèn)為是對(duì)空對(duì)象的引用,也歸屬于 Object 范疇,由此可見,typeof 是無法區(qū)分出引用數(shù)據(jù)類型的。
上面的示例中還有一個(gè)關(guān)鍵點(diǎn),那就是 function 函數(shù)。函數(shù)實(shí)際上也是對(duì)象,它并不代表一種數(shù)據(jù)類型,但它卻非常特殊。函數(shù)擁有對(duì)象的所有能力,但同時(shí)它自身還擁有特殊的屬性,并且與對(duì)象相比,函數(shù)還有一個(gè)特殊之處,就是它是可調(diào)用的,你可以手動(dòng)調(diào)用函數(shù)去執(zhí)行某個(gè)操作?;谝陨咸厥馇闆r,在 ECMAScript 規(guī)范中規(guī)定了可以通過 typeof 區(qū)分出函數(shù)和其它對(duì)象。
除了上述能檢測(cè)出的七種類型之外,幾乎其它所有類型經(jīng) typeof 檢測(cè)后都是返回 object,例如:
console.log( typeof document.children ); // object
console.log( typeof window ); // object
console.log( typeof document.querySelector( "html" ) ); // object
console.log( typeof document.createElement( "div" ) ); // object
console.log( typeof new Map() ); // object
console.log( typeof new Set() ); // object
console.log( typeof new Promise( () => {} ) ); // object
至此,可以得到一個(gè)初步結(jié)論,使用 typeof 運(yùn)算符只能檢測(cè)出:字符串、數(shù)字、布爾值、函數(shù)、Symbol、BigInt 和 undefined 七種類型,對(duì)于數(shù)組、對(duì)象、null 和其它類型則無能為力,需要另尋他法。
這里還需要說明一個(gè)特殊情況,對(duì)于字符串、數(shù)字、布爾值這三種基本數(shù)據(jù)類型,還存在對(duì)應(yīng)的特殊引用類型:
new String()new Number()new Boolean()
console.log( ( new String( "aa" ) ).valueOf() === "aa" ); // true console.log( ( new Number( 1234 ) ).valueOf() === 1234 ); // true console.log( ( new Boolean( true ) ).valueOf() === true ); // true
因此,一旦通過上述的方式創(chuàng)建字符串、數(shù)字或者布爾值,使用 typeof 將無法得到準(zhǔn)確的類型:
console.log( typeof new String( "aa" ) ); // object console.log( typeof new Number( 1234 ) ); // object console.log( typeof new Boolean( true ) ); // object
由此可見,typeof 運(yùn)算符對(duì)于字符串、數(shù)字和布爾值的類型判定,無法做到百分百的絕對(duì)精準(zhǔn)。不過,在實(shí)際開發(fā)中,基本上極少會(huì)遇到使用上述特殊方式創(chuàng)建這三種數(shù)據(jù)類型的情況。因此,仍然可以繼續(xù)使用 typeof 進(jìn)行判斷。
instanceof 運(yùn)算符
以下是 MDN 關(guān)于 instanceof 的描述:
instanceof運(yùn)算符用于檢測(cè)構(gòu)造函數(shù)的
prototype屬性是否出現(xiàn)在某個(gè)實(shí)例對(duì)象的原型鏈上。
語(yǔ)法:obj instanceof constructor
由于 instanceof 是基于 ”原型“ 的,因此它只適用于檢測(cè)引用數(shù)據(jù)類型,如:對(duì)象、數(shù)組等。
我們先來看一下示例:
const obj = {
a: "a"
};
console.log( obj instanceof Object ); // true
console.log( Object.getPrototypeOf( obj ) === Object.prototype ); // true
在上面的示例中,obj 是一個(gè)通過字面量形式創(chuàng)建的對(duì)象,本質(zhì)上相當(dāng)于 new Object(),也就是說,obj 是由 Object() 構(gòu)造函數(shù)構(gòu)建出來的,那么 obj 的原型鏈上必然包含 Object 的原型。
再看一個(gè)數(shù)組的例子:
const arr = [ 1, 2, 3 ]; console.log( arr instanceof Array ); // true
同樣的原理,arr 是一個(gè)通過字面量形式創(chuàng)建的數(shù)組,本質(zhì)上相當(dāng)于 new Array(),那 arr 的原型鏈上也必然包含 Array 的原型,因此,上面的邏輯是沒問題的,但是如果對(duì)代碼稍加改造,將 Array 換成 Object 會(huì)是什么結(jié)果呢?
const arr = [ 1, 2, 3 ]; console.log( arr instanceof Object ); // true
結(jié)果顯示也為 true,這是因?yàn)樵?JavaScript 中,數(shù)組其實(shí)也是對(duì)象,不僅僅是數(shù)組,凡是通過 new 關(guān)鍵字創(chuàng)建的實(shí)例本質(zhì)上都是對(duì)象。所以,前文提到的 typeof new xxx 的結(jié)果都是 object。也正因如此,數(shù)組的原型鏈中也必然包含 Object 的原型。
另外需要說明的是,instanceof 在多 iframe 環(huán)境下會(huì)存在問題,因?yàn)檫@意味著存在多個(gè)全局環(huán)境,而不同的全局環(huán)境擁有不同的全局對(duì)象,從而擁有不同的內(nèi)置類型構(gòu)造函數(shù),這將會(huì)導(dǎo)致 instanceof 出現(xiàn)混亂。
Object.prototype.toString.call()
這種絕妙的檢測(cè)方式最早是由 ”始祖級(jí)“ 的 JavaScript 類庫(kù) Prototype.js 發(fā)掘出來的。這幾乎要追溯到近 20 年前了,那時(shí)的前端還處在萌芽時(shí)期,各種規(guī)范標(biāo)準(zhǔn)尚未完善,還要面對(duì)令人抓狂的瀏覽器兼容問題,因此要想準(zhǔn)確檢測(cè)出各種數(shù)據(jù)類型簡(jiǎn)直是難如登天。各大程序庫(kù)想盡了辦法,各種奇技淫巧層出不窮,直到這種方式的出現(xiàn),終于有了一個(gè)穩(wěn)定的檢測(cè)方式,之后的庫(kù)和框架也基本都是用此方法來檢測(cè)數(shù)據(jù)類型。
它的根本原理實(shí)際上就是輸出對(duì)象內(nèi)部的類屬性 [[Class]] 的值,這在絕大多數(shù)情況下是肯定準(zhǔn)確的。這里先看第一個(gè)知識(shí)點(diǎn):toString。
簡(jiǎn)單來說,toString 方法就是將對(duì)象以字符串的形式返回。JavaScript 中幾乎所有對(duì)象都有 toString 方法,null 和 undefined 沒有 toString 方法,下面通過代碼示例看一下每種類型調(diào)用 toString 后返回的結(jié)果:
console.log( ( new String( "a" ) ).toString() ); // a
console.log( ( new Number( 100 ) ).toString() ); // 100
console.log( ( new Boolean( true ) ).toString() ); // true
console.log( [ 1,2,3 ].toString() ); // 1,2,3
console.log( { a: "a" }.toString() ); // [object Object]
console.log( Symbol().toString() ); // Symbol()
console.log( 100n.toString() ); // 100
上述結(jié)果可以看出,每個(gè)對(duì)象的 toString 方法都有自己的一套邏輯, 因此輸出的結(jié)果不盡相同,并且上面的結(jié)果也說明了,單純使用各自的 toString 方法得到的值也沒能表示出相關(guān)類型,只有一個(gè) [object Object] 值得研究。
為什么會(huì)得到 [object Object] 呢?這是因?yàn)閷?duì)象的 toString 方法無法將對(duì)象正確解析為字符串,所以 JavaScript 引擎直接返回了字符串 [object Object]。此時(shí)我們可以做出一個(gè)這樣的假設(shè):因?yàn)槭窃?object 類型的數(shù)據(jù)上調(diào)用了 toString 方法,返回了 [object Object],而這個(gè)字符串中的兩個(gè)單詞都是 object(先不考慮大小寫),能否說明這個(gè)字符串實(shí)際已經(jīng)包含了類型信息呢?如果這個(gè)假設(shè)成立,那么理論上其它類型的數(shù)據(jù)應(yīng)該也可以通過這種方式獲取到類型。但是前面提到了,每個(gè)對(duì)象的 toString 方法都有自己的一套邏輯,返回的內(nèi)容五花八門,現(xiàn)在就需要想辦法讓它們也能返回類似 [object Object] 這種形式的字符串,以此來推斷其所屬類型。這里就需要用到原型屬性,因?yàn)樗械膶?duì)象都繼承自 Object,既然它們各自的 toString 方法有自己的邏輯,那我們就不用他們自身的 toString,而是使用繼承自 Object 原型上的 toString, 也就是 Object.prototype.toString,那為什么后面還用了一個(gè) call 呢? 先來看一下不用 call 的結(jié)果:
console.log( Object.prototype.toString( [] ) ); // [object Object]
console.log( Object.prototype.toString( {} ) ); // [object Object]
console.log( Object.prototype.toString( "aa" ) ); // [object Object]
console.log( Object.prototype.toString( 11 ) ); // [object Object]
單純使用 Object.prototype.toString 將一律返回 [object Object],因?yàn)檫@始終是在調(diào)用 Object 的 toString 方法,其內(nèi)部的 this 始終指向的是 Object,所以就必須要借助 call 改變 this 的指向( apply 也可以 ), 所以才有了 Object.prototype.toString.call() 的寫法。其實(shí)可以這樣理解:我自己的 toString 被我重寫了,不能用了,那我就用 Object 的 toString,因?yàn)樗窃技儍舻模芊祷匚蚁胍臇|西,并且我繼承自 Object,能借用它的一切,自然也就能借用它的 toString,只需在借用時(shí)注明是我在使用就可以了( call 的作用 )。
下面就看看使用 Object.prototype.toString.call() 到底能否返回我們想要的結(jié)果吧。
console.log( Object.prototype.toString.call( "aa" ) ); // [object String]
console.log( Object.prototype.toString.call( 1000 ) ); // [object Number]
console.log( Object.prototype.toString.call( true ) ); // [object Boolean]
console.log( Object.prototype.toString.call( 100n ) ); // [object BigInt]
console.log( Object.prototype.toString.call( null ) ); // [object Null]
console.log( Object.prototype.toString.call( undefined ) ); // [object Undefined]
console.log( Object.prototype.toString.call( Symbol() ) ); // [object Symbol]
console.log( Object.prototype.toString.call( [ 1,2,3 ] ) ); // [object Array]
console.log( Object.prototype.toString.call( { a: "a" } ) ); // [object Object]
console.log( Object.prototype.toString.call( function () {} ) ); // [object Function]
再看看其它類型的數(shù)據(jù)
// [object Promise]
console.log( Object.prototype.toString.call( new Promise( () => {} ) ) );
// [object HTMLHtmlElement]
console.log( Object.prototype.toString.call( document.querySelector( "html" ) ) );
// [object HTMLDivElement]
console.log( Object.prototype.toString.call( document.createElement( "div" ) ) );
// [object HTMLCollection]
console.log( Object.prototype.toString.call( document.children ) );
// [object HTMLDocument]
console.log( Object.prototype.toString.call( document ) );
// [object Window]
console.log( Object.prototype.toString.call( window ) );
// [object Set]
console.log( Object.prototype.toString.call( new Set() ) );
// [object Map]
console.log( Object.prototype.toString.call( new Map() ) );
根據(jù)以上結(jié)果可以得知,返回結(jié)果都是以 [object 開頭,以 類型] 結(jié)尾,那么我們加工一下就可以用它直接返回類型了:
Object.prototype.toString.call( obj ).slice( 8, -1 );
那這個(gè)方法真的絕對(duì)保險(xiǎn)嗎?99% 的情況下是保險(xiǎn)的,但不排除極特殊情況,比如:
Object.prototype.toString = () => "哈哈哈"; console.log( Object.prototype.toString.call( "aa" ) ); // 哈哈哈 console.log( Object.prototype.toString.call( 1000 ) ); // 哈哈哈 console.log( Object.prototype.toString.call( true ) ); // 哈哈哈 console.log( Object.prototype.toString.call( 100n ) ); // 哈哈哈
由此可見,如果最原始的 Object.prototype.toString 被改寫了,那么這個(gè)方法就失效了,不過正常情況下誰會(huì)這樣做呢?
封裝示例
基于以上各種檢測(cè)手段,我們可以封裝一個(gè)基本的類型檢測(cè)方法,下面是一個(gè)最基本的封裝示例,大家可以自行完善。
function getType ( data ) {
// 對(duì)于簡(jiǎn)單的類型直接使用 typeof 判斷
let type = "";
switch ( typeof data ) {
case "string": type === "string"; break;
case "number": type === "number"; break;
case "boolean": type === "boolean"; break;
case "function": type === "function"; break;
case "symbol": type === "symbol"; break;
case "bigint": type === "bigint"; break;
case "undefined": type === "undefined"; break;
}
if ( type ) {
return type;
}
// 數(shù)組類型直接使用原生提供的 Array.isArray
if ( Array.isArray( data ) ) {
return "array";
}
// 其余類型使用 Object.prototype.toString.call 獲取
return Object.prototype.toString.call( data ).slice( 8, -1 ).toLowerCase();
}
以上就是JavaScript獲取數(shù)據(jù)類型的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript獲取數(shù)據(jù)類型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS控制一個(gè)DIV層在指定時(shí)間內(nèi)消失的方法
這篇文章主要介紹了JS控制一個(gè)DIV層在指定時(shí)間內(nèi)消失的方法,需要的朋友可以參考下2014-02-02
JavaScript學(xué)習(xí)筆記之創(chuàng)建對(duì)象
在JavaScript中對(duì)象是一種基本的數(shù)據(jù)類型,在數(shù)據(jù)結(jié)構(gòu)上是一種散列表,可以看作是屬性的無序集合,除了原始值其他一切都是對(duì)象。這篇文章主要給大家介紹JavaScript學(xué)習(xí)筆記之創(chuàng)建對(duì)象,需要的朋友參考下吧2016-03-03
js中base64、url和blob之間相互轉(zhuǎn)換的3種方式(詳細(xì)代碼)
這篇文章主要給大家介紹了關(guān)于js中base64、url和blob之間相互轉(zhuǎn)換的3種方式,Blob和File是用來表示二進(jìn)制數(shù)據(jù)的,而Base64則是一種編碼方式,用來把二進(jìn)制數(shù)據(jù)編碼成可讀的字符串,需要的朋友可以參考下2023-10-10
javaScript產(chǎn)生隨機(jī)數(shù)的用法小結(jié)
這篇文章主要介紹了javaScript產(chǎn)生隨機(jī)數(shù)的用法小結(jié),包括JavaScript Math.random()內(nèi)置函數(shù) ,Js 隨機(jī)數(shù)產(chǎn)生6位數(shù)字的代碼,需要的朋友可以參考下2018-04-04
js子頁(yè)面獲取父頁(yè)面數(shù)據(jù)示例
這篇文章主要介紹了js子頁(yè)面如何獲取父頁(yè)面數(shù)據(jù),需要的朋友可以參考下2014-05-05
js 動(dòng)態(tài)加載事件的幾種方法總結(jié)
本篇文章主要是對(duì)js 動(dòng)態(tài)加載事件的幾種方法進(jìn)行了詳細(xì)的總結(jié)介紹,需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-12-12
Javascript 代碼也可以變得優(yōu)美的實(shí)現(xiàn)方法
Javascript 代碼也可以變得優(yōu)美的一些經(jīng)驗(yàn)小結(jié)。2009-06-06

