App下載

判斷JS數(shù)據(jù)類型的四種方法

猿友 2021-02-09 23:14:57 瀏覽數(shù) (6130)
反饋

在 ECMAScript 規(guī)范中,共定義了 7 種數(shù)據(jù)類型,分為 基本類型 和 引用類型 兩大類,如下所示:

基本類型:String、Number、Boolean、Symbol、Undefined、Null 引用類型:Object

基本類型也稱為簡單類型,由于其占據(jù)空間固定,是簡單的數(shù)據(jù)段,為了便于提升變量查詢速度,將其存儲在棧中,即按值訪問。

引用類型也稱為復(fù)雜類型,由于其值的大小會改變,所以不能將其存放在棧中,否則會降低變量查詢速度,因此,其值存儲在堆(heap)中,而存儲在變量處的值,是一個指針,指向存儲對象的內(nèi)存處,即按址訪問。引用類型除 Object 外,還包括 Function 、Array、RegExp、Date 等等。

鑒于 ECMAScript 是松散類型的,因此需要有一種手段來檢測給定變量的數(shù)據(jù)類型。對于這個問題,JavaScript 也提供了多種方法,但遺憾的是,不同的方法得到的結(jié)果參差不齊。

下面介紹常用的4種方法,并對各個方法存在的問題進行簡單的分析。

1、typeof

typeof 是一個操作符,其右側(cè)跟一個一元表達式,并返回這個表達式的數(shù)據(jù)類型。返回的結(jié)果用該類型的字符串(全小寫字母)形式表示,包括以下 7 種:number、boolean、symbol、string、object、undefined、function 等。

typeof'';// string 有效

typeof1;// number 有效

typeofSymbol();// symbol 有效

typeoftrue;//boolean 有效

typeofundefined;//undefined 有效

typeofnull;//object 無效

typeof[] ;//object 無效

typeofnewFunction();// function 有效

typeofnewDate();//object 無效

typeofnewRegExp();//object 無效

有些時候,typeof 操作符會返回一些令人迷惑但技術(shù)上卻正確的值:

  • 對于基本類型,除 null 以外,均可以返回正確的結(jié)果。
  • 對于引用類型,除 function 以外,一律返回 object 類型。
  • 對于 null ,返回 object 類型。
  • 對于 function 返回 function 類型。

其中,null 有屬于自己的數(shù)據(jù)類型 Null , 引用類型中的 數(shù)組、日期、正則 也都有屬于自己的具體類型,而 typeof 對于這些類型的處理,只返回了處于其原型鏈最頂端的 Object 類型,沒有錯,但不是我們想要的結(jié)果。

2、instanceof

instanceof 是用來判斷 A 是否為 B 的實例,表達式為:A instanceof B,如果 A 是 B 的實例,則返回 true,否則返回 false。 在這里需要特別注意的是:instanceof 檢測的是原型,我們用一段偽代碼來模擬其內(nèi)部執(zhí)行過程:

instanceof (A,B) = {

varL = A.__proto__;

varR = B.prototype;

if(L === R) {

// A的內(nèi)部屬性 __proto__ 指向 B 的原型對象

returntrue;

}

returnfalse;

}

從上述過程可以看出,當(dāng) A 的 proto 指向 B 的 prototype 時,就認為 A 就是 B 的實例,我們再來看幾個例子:

[] instanceof Array;// true

{} instanceof Object;// true

newDate() instanceof Date;// true

function Person(){};

newPerson() instanceof Person;

[] instanceof Object;// true

newDate() instanceof Object;// true

newPerson instanceof Object;// true

我們發(fā)現(xiàn),雖然 instanceof 能夠判斷出 [ ] 是Array的實例,但它認為 [ ] 也是Object的實例,為什么呢?

我們來分析一下 [ ]、Array、Object 三者之間的關(guān)系:

從 instanceof 能夠判斷出 [ ].proto  指向 Array.prototype,而 Array.prototype.proto 又指向了Object.prototype,最終 Object.prototype.proto 指向了null,標(biāo)志著原型鏈的結(jié)束。因此,[]、Array、Object 就在內(nèi)部形成了一條原型鏈:

https://images2015.cnblogs.com/blog/849589/201601/849589-20160112232510850-2003340583.png

從原型鏈可以看出,[] 的 proto  直接指向Array.prototype,間接指向 Object.prototype,所以按照 instanceof 的判斷規(guī)則,[] 就是Object的實例。依次類推,類似的 new Date()、new Person() 也會形成一條對應(yīng)的原型鏈 。因此,instanceof 只能用來判斷兩個對象是否屬于實例關(guān)系, 而不能判斷一個對象實例具體屬于哪種類型。

instanceof 操作符的問題在于,它假定只有一個全局執(zhí)行環(huán)境。如果網(wǎng)頁中包含多個框架,那實際上就存在兩個以上不同的全局執(zhí)行環(huán)境,從而存在兩個以上不同版本的構(gòu)造函數(shù)。如果你從一個框架向另一個框架傳入一個數(shù)組,那么傳入的數(shù)組與在第二個框架中原生創(chuàng)建的數(shù)組分別具有各自不同的構(gòu)造函數(shù)。

variframe = document.createElement('iframe');

document.body.appendChild(iframe);

xArray = window.frames[0].Array;

vararr =newxArray(1,2,3);// [1,2,3]

arr instanceof Array;// false

if(Array.isArray(value)){

//對數(shù)組執(zhí)行某些操作

}

Array.isArray() 本質(zhì)上檢測的是對象的 [[Class]] 值,[[Class]] 是對象的一個內(nèi)部屬性,里面包含了對象的類型信息,其格式為 [object Xxx] ,Xxx 就是對應(yīng)的具體類型 。對于數(shù)組而言,[[Class]] 的值就是 [object Array] 。

3、constructor

當(dāng)一個函數(shù) F被定義時,JS引擎會為F添加 prototype 原型,然后再在 prototype上添加一個 constructor 屬性,并讓其指向 F 的引用。如下所示:

https://images2015.cnblogs.com/blog/849589/201705/849589-20170508125250566-1896556617.png

當(dāng)執(zhí)行 var f = new F() 時,F(xiàn) 被當(dāng)成了構(gòu)造函數(shù),f 是F的實例對象,此時 F 原型上的 constructor 傳遞到了 f 上,因此 f.constructor == F

https://images2015.cnblogs.com/blog/849589/201705/849589-20170508125714941-1649387639.png

可以看出,F(xiàn) 利用原型對象上的 constructor 引用了自身,當(dāng) F 作為構(gòu)造函數(shù)來創(chuàng)建對象時,原型上的 constructor 就被遺傳到了新創(chuàng)建的對象上, 從原型鏈角度講,構(gòu)造函數(shù) F 就是新對象的類型。這樣做的意義是,讓新對象在誕生以后,就具有可追溯的數(shù)據(jù)類型。

同樣,JavaScript 中的內(nèi)置對象在內(nèi)部構(gòu)建時也是這樣做的:

https://images2015.cnblogs.com/blog/849589/201705/849589-20170508131800457-2091987664.png

細節(jié)問題:

null 和 undefined 是無效的對象,因此是不會有 constructor 存在的,這兩種類型的數(shù)據(jù)需要通過其他方式來判斷。2. 函數(shù)的 constructor 是不穩(wěn)定的,這個主要體現(xiàn)在自定義對象上,當(dāng)開發(fā)者重寫 prototype 后,原有的 constructor 引用會丟失,constructor 會默認為 Object

https://images2015.cnblogs.com/blog/849589/201705/849589-20170508132757347-1999338357.png

為什么變成了 Object?

因為 prototype 被重新賦值的是一個 { }, { } 是 new Object() 的字面量,因此 new Object() 會將 Object 原型上的 constructor 傳遞給 { },也就是 Object 本身。

因此,為了規(guī)范開發(fā),在重寫對象原型時一般都需要重新給 constructor 賦值,以保證對象實例的類型不被篡改。

4、toString

toString() 是 Object 的原型方法,調(diào)用該方法,默認返回當(dāng)前對象的 [[Class]] 。這是一個內(nèi)部屬性,其格式為 [object Xxx] ,其中 Xxx 就是對象的類型。

對于 Object 對象,直接調(diào)用 toString()  就能返回 [object Object] 。而對于其他對象,則需要通過 call / apply 來調(diào)用才能返回正確的類型信息。

Object.prototype.toString.call('') ;  // [object String]

Object.prototype.toString.call(1) ;   // [object Number]

Object.prototype.toString.call(true) ;// [object Boolean]

Object.prototype.toString.call(Symbol());//[object Symbol]

Object.prototype.toString.call(undefined) ;// [object Undefined]

Object.prototype.toString.call(null) ;// [object Null]

Object.prototype.toString.call(newFunction()) ;// [object Function]

Object.prototype.toString.call(newDate()) ;// [object Date]

Object.prototype.toString.call([]) ;// [object Array]

Object.prototype.toString.call(newRegExp()) ;// [object RegExp]

Object.prototype.toString.call(newError()) ;// [object Error]

Object.prototype.toString.call(document) ;// [object HTMLDocument]

Object.prototype.toString.call(window) ;//[object global] window 是全局對象 global 的引用


0 人點贊