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