在 ECMAScript 規范中,共定義了 7 種數據類型,分為基本類型和引用類型兩大類。
其中:
基本類型:String、Number、Boolean、Symbol、Undefined、Null
引用類型:Object
基本類型也稱為簡單類型,由於其占據空間固定,是簡單的數據段,為了便於提升變量查詢速度,將其存儲在棧(stack)中,即按值訪問。
引用類型也稱為復雜類型,由於其值的大小會改變,所以不能將其存放在棧中,否則會降低變量查詢速度,因此,其值存儲在堆(heap)中,而存儲在變量處的值,是一個指針,指向存儲對象的內存處,即按址訪問。引用類型除 Object 外,還包括 Function 、Array、RegExp、Date 等等。
下面介紹常用的4種判斷數據類型的方法,並對各個方法存在的問題進行簡單的分析。
一、typeof
typeof 是一個操作符,其右側跟一個一元表達式,並返回這個表達式的數據類型。
返回的結果用該類型的字符串(全小寫字母)形式表示,包括以下 7 種:string、number、boolean、symbol、undefined、object、function 等。
typeof 'a'; // string 有效 typeof 1; // number 有效 typeof true; //boolean 有效 typeof Symbol(); // symbol 有效 typeof undefined; //undefined 有效 typeof new Function(); // function 有效 typeof null; //object 無效 typeof [1] ; //object 無效 typeof new RegExp(); //object 無效 typeof new Date(); //object 無效
總結:
- 對於基本類型,除 null 以外,均可以返回正確的結果。
- 對於引用類型,除 function 以外,一律返回 object 類型。
- 對於 null ,返回 object 類型。
- 對於 function 返回 function 類型。
其中,null 有屬於自己的數據類型 Null ,引用類型中的數組、日期、正則 也都有屬於自己的具體類型,而 typeof 對於這些類型的處理,只返回了處於其原型鏈最頂端的 Object 類型。
二、instanceof
instanceof 檢測的是原型,表達式為:A instanceof B,如果 A 是 B 的實例,則返回 true,否則返回 false。
[] instanceof Array; // true new Date() instanceof Date;// true function Person(){}; new Person() instanceof Person;//true [] instanceof Object; // true new Date() instanceof Object;// true new Person instanceof Object;// true
雖然 instanceof 能夠判斷出 [ ] 是Array的實例,但它認為 [ ] 也是Object的實例,為什么呢?
從 instanceof 能夠判斷出 [ ].__proto__ 指向 Array.prototype,而 Array.prototype.__proto__ 又指向了Object.prototype,最終 Object.prototype.__proto__ 指向了null,標志着原型鏈的結束。因此,[ ]、Array、Object 就在內部形成了一條原型鏈,如下圖所示:
從原型鏈可以看出,[] 的 __proto__ 直接指向Array.prototype,間接指向 Object.prototype,所以按照 instanceof 的判斷規則,[] 就是Object的實例。依次類推,類似的 new Date()、new Person() 也會形成一條對應的原型鏈 。
因此,instanceof 只能用來判斷兩個對象是否屬於實例關系, 而不能判斷一個對象實例具體屬於哪種類型。
針對數組的這個問題,ES5 提供了 Array.isArray() 方法 。該方法用以確認某個對象本身是否為 Array 類型,而不區分該對象在哪個環境中創建。
if (Array.isArray(value)){ //對數組執行某些操作 }
Array.isArray() 本質上檢測的是對象的 [[Class]] 值。
[[Class]] 是對象的一個內部屬性,里面包含了對象的類型信息,其格式為 [object Xxx],Xxx 就是對應的具體類型 。對於數組而言,[[Class]] 的值就是 [object Array] 。
三、constructor
當一個函數 F被定義時,JS引擎會為F添加 prototype 原型,然后再在 prototype上添加一個 constructor 屬性,並讓其指向 F 的引用。如下圖所示:
當執行 var f = new F() 時,F 被當成了構造函數,f 是F的實例對象,此時 F 原型上的 constructor 傳遞到了 f 上,因此 f.constructor == F
可以看出,F 利用原型對象上的 constructor 引用了自身,當 F 作為構造函數來創建對象時,原型上的 constructor 就被遺傳到了新創建的對象上, 從原型鏈角度講,構造函數 F 就是新對象的類型。這樣做的意義是,讓新對象在誕生以后,就具有可追溯的數據類型。
同樣,JavaScript 中的內置對象在內部構建時也是這樣做的,如下圖所示。
總結:
1. null 和 undefined 是無效的對象,因此是不會有 constructor 存在的,這兩種類型的數據需要通過其他方式來判斷。
2. 函數的 constructor 是不穩定的,這個主要體現在自定義對象上,當開發者重寫 prototype 后,原有的 constructor 引用會丟失,constructor 會默認為 Object。
四、toString
toString() 是 Object 的原型方法,調用該方法,默認返回當前對象的 [[Class]] 。這是一個內部屬性,其格式為 [object Xxx] ,其中 Xxx 就是對象的類型。
對於 Object 對象,直接調用 toString() 就能返回 [object Object] 。而對於其他對象,則需要通過 call / apply 來調用才能返回正確的類型信息。
語法:Object.prototype.toString.call(value);
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(new Function()) ; // [object Function] Object.prototype.toString.call(new Date()) ; // [object Date] Object.prototype.toString.call([]) ; // [object Array] Object.prototype.toString.call(new RegExp()) ; // [object RegExp] Object.prototype.toString.call(new Error()) ; // [object Error] Object.prototype.toString.call(document) ; // [object HTMLDocument] Object.prototype.toString.call(window) ; //[object global] window 是全局對象 global 的引用