關於JS資料型別檢測的多種方式總結
目錄
- 背景
- 判斷資料型別的手段有哪些?
- 1. 用typeof判斷基礎資料型別:
- 2. 用instanceof判斷物件資料型別
- 3. 用contructor屬性
- 4. toString方法
- 5. 用Array.isArray判斷陣列
- 6.區分ArrayLike與Array
- 7.判斷一個物件是否是純物件(or普通物件)
- 8. NaN如何檢測,Number.isNaN與isNaN有啥區別
- 9. 鴨式型別檢測法
- 總結
背景
總所周知,是一門動態的弱型別語言,其採用動態的型別系統以及基於原型的繼承方式。
缺乏型別的靜態約束,這意味著資料型別導致的程式錯誤並不能在編譯階段及時發現,要想寫出健壯的程式碼,就必須在執行時各種的check&相容,所以能夠熟練準確的檢測資料型別成為掌握這門語言最重要的基礎之一。
判斷資料型別的手段有哪些?
總的來說大致有以下幾種:typeof、instanceof、Object.prototype.toString、constructor、鴨式型別、及針對特定型別的檢測方法Array.isArray(),Number.isNaN(),雖然方法很多,但他們的使用場景有所不同。
1. 用typeof判斷基礎資料型別:
返回值有undefined、string、number、boolean、object、function、symbol七種。
可以看出,typeof作為官方提供的型別檢測操作符,在檢測undefined、string、boolean、symbol這些基本資料型別及function方面是十分靠譜的。表現拉垮的地方主要在於
1) 不能對具體物件型別(Array、Date、regExp)進行區分。
2) typeof null === 'object' // 竟然是true。。。。
缺陷 2)可以避免,在判斷物件引用型別時多判斷一句即可,typeof x === 'object' && x !== null。但是不能區分物件的具體型別,確實是個很大痛點。
2. 用instanceof判斷物件資料型別
此運算子用www.cppcns.com於檢測某個建構函式的prototype是否出現在目標物件的原型鏈上。
這是一種預測的檢測方式,並不會像typeof一樣直接將資料型別以字串的方式進行返回,而是你需要預判物件型別的建構函式,最終返回一個boolean值。
檢http://www.cppcns.com測規則其實從命名就可以看出,判斷例項是否是由某個建構函式所建立的,那麼知道了原理,現在動手實現一個屬於自己的instanceof。
function myInstanceof(target,constructor){ const baseType = ['string','number','boolean','undefined','symbol'] if(baseType.includes(typeof(target))) { return false } //原型鏈其實就是個物件組成的連結串列,遍歷這個連結串列, let prototype = Object.getPrototypeOf(target); while(prototype){ //一旦鏈上有物件有符合,就返回true if(prototype === constructor.prototype){ return true }else{ prototype = Object.getPrototypeOf(prototype) } } return false } console.log(myInstanceof([],Array))
在js裡,可以從廣義上認為萬物源於物件,因為例項雖然是通過建構函式建立的,但是建構函式本身只是沒有感情的生產機器,例項的靈魂和性格(公共屬性和方法)都是共享自建構函式的prototype屬性指向的那個原型物件,而且原型物件都是純物件,純物件又是由Object建構函式建立的,那麼就會造成下邊這種後果。
對於陣列,遍歷原型鏈上的物件,Array.prototype Object.prototype都會出現。
並且,對字面量方式建立的基本資料型別無法進行判斷。比如
如何彌補上邊的缺陷呢,答案是可以在上邊特殊的場景中採用下邊的constructor代替instanceof。
3. 用contructor屬性
首先先明確。constructor是原型上的屬性,例項繼承自原型,所以例項上也能直接訪問此屬性。
首先看下contructor的通用性表現
意外的表現不錯,除了null、undefined,有contructor屬性的基礎(包裝)型別或者物件型別都能準確判斷。
能準確區分Array|Object 因為它沒有instanceof那樣會遍歷整條原型鏈,只是在例項身上進行判斷。但也有個致命的缺陷,例項上的這一屬性太容易被修改了,一旦修改,這個方法就沒有意義了。
4. toString方法
首先,js的物件型別或者基礎型別的包裝物件都有一個toString方法。繼承自Object.prototype.toString(),呼叫會返回對應型別的字串標記"[object Type]"。
這個方法有種亂拳打死老師傅,無心插柳柳成蔭的感覺,本來的作用只是得到一個表示該物件的字串,現在用在js型別檢測上,表現簡直不要太好,針對基礎型別及物件型別表現都非常不錯,如果非要說個缺點,只能說返回的字串有點複雜,使用不太方便,現在讓我們動手簡化一下。
先寫一個簡版
function isTywww.cppcns.compe(type,value){
return Object.prototype.toString.call(value) === `[object ${type}]`
}
console.log(isType('Array',[]))
console.log(isType('Number',1))
這樣使用也不太方便,‘Array' ‘Number'這樣的型別引數,很容易拼寫錯誤,所以希望方法可以預設引數,並且希望構造一個函式工廠,呼叫返回類似於isArray這樣的函式。在IDE中函式名相比字串會擁有更好的程式碼提示,不容易拼寫錯誤。
function isType(type){ return function(value){ return Object.prototype.toString.call(value) === `[object ${type}]` } } const isArray = isType('Array') const isNumber = isType('Number') console.log(isArray([]),isNumber(1))
這裡運用了高階函式的思想,保留引數+返回一個新的函式,那麼可以想到js裡bind除了可以繫結this,也有保留引數+返回新函式的功能,用在這裡也很合適。
function isType(type,value){ return Object.prototype.toString.call(value) === `[object ${type}]` } const isArray = isType.bind(null,'Array') const isNumber = isType.bind(null,'Number') console.log(isArray([]),isNumber(1))
更進一步,用引數柯里化的思想改造一波
function isType(type,value){ return Object.prototype.toString.call(value) === `[object ${type}]` } function curring (fn,...args1){ let len = fn.length; return function(...args2){ const args = args1.concat(args2); if(args.length < len){ return curring(fn,...args) }else{ return fn(...args) } } } const isArray = curring(isType,'Array') const isNumber = curring(isType,isNumber(1))
最後,豐富一下支援的型別,大功告成。
const types = [ 'Null','Undefined','String','Number','Boolean','Object','Array','Date','Function','RegExp','Symbol','Math',] const checkTypeUtil = {} types.forEach((type)=>{ checkTypeUtil[`is${type}`] = curring(isType,type) }) export { checkTypeUtil } console.log(checkTypeUtil.isArray([]))
5. 用Array.isArray判斷陣列
上邊提到 instanceof可以用來檢測陣列,但是這在iframe建立的多window環境中,因為window全域性環境需要隔離,所以Array和Array.prototype在每個視窗中必須是不同的,所以iframeA.Array.prototype ≠ iframeB.Array.prototype,所以 iframeA.arr instanceof iframeB.Array必定是返回false,這是小概率的事件,但是在使用iframe的場景裡,互相傳值,也是非常可能發生的。使用ES6提供的Array.isArray就沒有這個問題,可以準確判斷陣列。
可以這樣 pollify
if (!Array.isArray) { Array.isArray = function(x) { return Object.prototype.toString.call(x) === '[object Array]'; }; }
6.區分ArrayLike與Array
類陣列的定義是:
- 擁有length屬性,其它屬性(索引)為非負整數(物件中的索引會被當做字串來處理
- 不具有陣列所具有的方法
function isLikeArray(x){ if(!(typeof x === 'object' && x !== null)){ return false } return typeof x.length === 'number' && x.length >= 0 && !Array.isArray(x) }
類陣列可以用Array.from Array.prototype.slice.call(val)來轉換為真正的陣列。
7.判斷一個物件是否是純物件(or普通物件)
純物件的定義:特指通過一下三種方式建立的物件
- new Object
- 物件字面量建立 {}
- Object.create(null)
、lodash原始碼都是採用下邊的方法來檢測
const funcToString = Function.prototype.toString const objectCtorString = funcToString.call(Object) function isPlainObject(value){ // 先用toString先排除其他資料型別 if(!value || !Object.prototype.toString.call(value) === "[object Object]"){ return false } const proto = Object.getPrototypeOf(value) if(proto === null){//相容Object.create(null)這樣建立的物件 return true } const Ctor = Object.prototype.hasOwnProperty.call(proto,'constructor') && proto.constructor; if(typeof Ctor !== 'function'){ return false } // 這裡通過字串判斷建構函式是否是Object,而不是直接使用instanceof,是為了避免上邊提到的 多window環境Object不同的問題 if(funcToString.call(Ctor) === objectCtorString){ return true } return false } console.log(isPlainObject(Object.create(null))) console.log(isPlainObject(new Object)) console.log(isPlainObject({a:1}))
8. NaN如何檢測,Number.isNaN與isNaN有啥區別
結論:Number.isNaN會嚴格的判斷傳入的值是否是直接等於NaN。
isNaN則會先進行Number()轉換,然後再進行是否是NaN的判斷。
9. 鴨式型別檢測法
其實上邊利用constuctor判斷資料型別,就是採用了這種方法。判斷一個動物是不是鴨子,那麼通過看起來像鴨子,叫起來像鴨子這樣簡單的經驗判斷就可大致進行判斷。
比如判斷一個物件是不是一個Promise,就可以這樣
function isPromise(x){ if(!(x instanceof Promise)){ return false } return typeof x.then === 'function' }
總結
到此這篇關於JS資料型別檢hsMKEu測的文章就介紹到這了,更多相關JS資料型別檢測內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!