在JavaScript中,有5種基本資料型別和1種複雜資料型別,基本資料型別有:Undefined, Null, Boolean, Number和String;複雜資料型別是Object,Object中還細分了很多具體的型別,比如:Array, Function, Date等等。今天我們就來探討一下,使用什麼方法判斷一個出一個變數的型別。
在講解各種方法之前,我們首先定義出幾個測試變數,看看後面的方法究竟能把變數的型別解析成什麼樣子,以下幾個變數差不多包含了我們在實際編碼中常用的型別。
1 2 3 4 5 6 7 8 9 10 11 |
var num = 123; var str = 'abcdef'; var bool = true; var arr = [1, 2, 3, 4]; var json = {name:'wenzi', age:25}; var func = function(){ console.log('this is function'); } var und = undefined; var nul = null; var date = new Date(); var reg = /^[a-zA-Z]{5,20}$/; var error= new Error(); |
1. 使用typeof檢測
我們平時用的最多的就是用typeof檢測變數型別了。這次,我們也使用
typeof檢測變數的型別:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
console.log( typeof num, typeof str, typeof bool, typeof arr, typeof json, typeof func, typeof und, typeof nul, typeof date, typeof reg, typeof error ); // number string boolean object object function undefined object object object object |
從輸出的結果來看,arr, json, nul, date, reg, error 全部被檢測為object型別,其他的變數能夠被正確檢測出來。當需要變數是否是
number,
string,
boolean,
function,
undefined, json型別時,可以使用typeof進行判斷。其他變數是判斷不出型別的,包括null。
還有,typeof是區分不出array和
json型別的。因為使用typeof這個變數時,array和json型別輸出的都是
object。
2. 使用instance檢測
在 JavaScript 中,判斷一個變數的型別嚐嚐會用 typeof 運算子,在使用 typeof 運算子時採用引用型別儲存值會出現一個問題,無論引用的是什麼型別的物件,它都返回 “object”。ECMAScript 引入了另一個 Java 運算子 instanceof 來解決這個問題。instanceof 運算子與 typeof 運算子相似,用於識別正在處理的物件的型別。與 typeof 方法不同的是,instanceof 方法要求開發者明確地確認物件為某特定型別。例如:
1 2 3 4 5 |
function Person(){ } var Tom = new Person(); console.log(Tom instanceof Person); // true |
我們再看看下面的例子:
1 2 3 4 5 6 7 8 9 10 |
function Person(){ } function Student(){ } Student.prototype = new Person(); var John = new Student(); console.log(John instanceof Student); // true console.log(John instancdof Person); // true |
instanceof還能檢測出多層繼承的關係。
好了,我們來使用instanceof檢測上面的那些變數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
console.log( num instanceof Number, str instanceof String, bool instanceof Boolean, arr instanceof Array, json instanceof Object, func instanceof Function, und instanceof Object, nul instanceof Object, date instanceof Date, reg instanceof RegExp, error instanceof Error ) // num : false // str : false // bool : false // arr : true // json : true // func : true // und : false // nul : false // date : true // reg : true // error : true |
從上面的執行結果我們可以看到,num, str和bool沒有檢測出他的型別,但是我們使用下面的方式建立num,是可以檢測出型別的:
1 2 3 |
var num = new Number(123); var str = new String('abcdef'); var boolean = new Boolean(true); |
同時,我們也要看到,und和nul是檢測的Object型別,才輸出的true,因為js中沒有Undefined和
Null的這種全域性型別,他們und和nul都屬於Object型別,因此輸出了true。
3. 使用constructor檢測
在使用instanceof檢測變數型別時,我們是檢測不到
number, 'string',
bool的型別的。因此,我們需要換一種方式來解決這個問題。
constructor本來是原型物件上的屬性,指向建構函式。但是根據例項物件尋找屬性的順序,若例項物件上沒有例項屬性或方法時,就去原型鏈上尋找,因此,例項物件也是能使用constructor屬性的。
我們先來輸出一下num.constructor的內容,即數字型別的變數的建構函式是什麼樣子的:
1 |
function Number() { [native code] } |
我們可以看到它指向了Number的建構函式,因此,我們可以使用
num.constructor==Number來判斷num是不是Number型別的,其他的變數也類似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function Person(){ } var Tom = new Person(); // undefined和null沒有constructor屬性 console.log( Tom.constructor==Person, num.constructor==Number, str.constructor==String, bool.constructor==Boolean, arr.constructor==Array, json.constructor==Object, func.constructor==Function, date.constructor==Date, reg.constructor==RegExp, error.constructor==Error ); // 所有結果均為true |
從輸出的結果我們可以看出,除了undefined和null,其他型別的變數均能使用constructor判斷出型別。
不過使用constructor也不是保險的,因為constructor屬性是可以被修改的,會導致檢測出的結果不正確,例如:
1 2 3 4 5 6 7 8 9 10 |
function Person(){ } function Student(){ } Student.prototype = new Person(); var John = new Student(); console.log(John.constructor==Student); // false console.log(John.constructor==Person); // true |
在上面的例子中,Student原型中的constructor被修改為指向到Person,導致檢測不出例項物件John真實的建構函式。
同時,使用instaceof和construcor,被判斷的array必須是在當前頁面宣告的!比如,一個頁面(父頁面)有一個框架,框架中引用了一個頁面(子頁面),在子頁面中宣告瞭一個array,並將其賦值給父頁面的一個變數,這時判斷該變數,Array == object.constructor;會返回false;
原因:
1、array屬於引用型資料,在傳遞過程中,僅僅是引用地址的傳遞。
2、每個頁面的Array原生物件所引用的地址是不一樣的,在子頁面宣告的array,所對應的建構函式,是子頁面的Array物件;父頁面來進行判斷,使用的Array並不等於子頁面的Array;切記,不然很難跟蹤問題!
4. 使用Object.prototype.toString.call
我們先不管這個是什麼,先來看看他是怎麼檢測變數型別的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
console.log( Object.prototype.toString.call(num), Object.prototype.toString.call(str), Object.prototype.toString.call(bool), Object.prototype.toString.call(arr), Object.prototype.toString.call(json), Object.prototype.toString.call(func), Object.prototype.toString.call(und), Object.prototype.toString.call(nul), Object.prototype.toString.call(date), Object.prototype.toString.call(reg), Object.prototype.toString.call(error) ); // '[object Number]' '[object String]' '[object Boolean]' '[object Array]' '[object Object]' // '[object Function]' '[object Undefined]' '[object Null]' '[object Date]' '[object RegExp]' '[object Error]' |
從輸出的結果來看,Object.prototype.toString.call(變數)輸出的是一個字串,字串裡有一個陣列,第一個引數是Object,第二個引數就是這個變數的型別,而且,所有變數的型別都檢測出來了,我們只需要取出第二個引數即可。或者可以使用
Object.prototype.toString.call(arr)=="object Array"來檢測變數arr是不是陣列。
我們現在再來看看ECMA裡是是怎麼定義Object.prototype.toString.call的:
Object.prototype.toString( ) When the toString method is called, the following steps are taken:
1. Get the [[Class]] property of this object.
2. Compute a string value by concatenating the three strings “[object “, Result (1), and “]”.
3. Return Result (2)
上面的規範定義了Object.prototype.toString的行為:首先,取得物件的一個內部屬性[[Class]],然後依據這個屬性,返回一個類似於”[object Array]”的字串作為結果(看過ECMA標準的應該都知道,[[]]用來表示語言內部用到的、外部不可直接訪問的屬性,稱為“內部屬性”)。利用這個方法,再配合call,我們可以取得任何物件的內部屬性[[Class]],然後把型別檢測轉化為字串比較,以達到我們的目的。
5. jquery中$.type的實現
在jquery中提供了一個$.type的介面,來讓我們檢測變數的型別:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
console.log( $.type(num), $.type(str), $.type(bool), $.type(arr), $.type(json), $.type(func), $.type(und), $.type(nul), $.type(date), $.type(reg), $.type(error) ); // number string boolean array object function undefined null date regexp error |
看到輸出結果,有沒有一種熟悉的感覺?對,他就是上面使用Object.prototype.toString.call(變數)輸出的結果的第二個引數呀。
我們這裡先來對比一下上面所有方法檢測出的結果,橫排是使用的檢測方法, 豎排是各個變數:
型別判斷 | typeof | instanceof | constructor | toString.call | $.type |
num | number | false | true | [object Number] | number |
str | string | false | true | [object String] | string |
bool | boolean | false | true | [object Boolean] | boolean |
arr | object | true | true | [object Array] | array |
json | object | true | true | [object Object] | object |
func | function | true | true | [object Function] | function |
und | undefined | false | – | [object Undefined] | undefined |
nul | object | false | – | [object Null] | null |
date | object | true | true | [object Date] | date |
reg | object | true | true | [object RegExp] | regexp |
error | object | true | true | [object Error] | error |
優點 | 使用簡單,能直接輸出結果 | 能檢測出複雜的型別 | 基本能檢測出所有的型別 | 檢測出所有的型別 | – |
缺點 | 檢測出的型別太少 | 基本型別檢測不出,且不能跨iframe | 不能跨iframe,且constructor易被修改 | IE6下undefined,null均為Object | – |
這樣對比一下,就更能看到各個方法之間的區別了,而且Object.prototype.toString.call和
$type輸出的結果真的很像。我們來看看jquery(2.1.2版本)內部是怎麼實現$.type方法的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 例項物件是能直接使用原型鏈上的方法的 var class2type = {}; var toString = class2type.toString; // 省略部分程式碼... type: function( obj ) { if ( obj == null ) { return obj + ""; } // Support: Android<4.0, iOS<6 (functionish RegExp) return (typeof obj === "object" || typeof obj === "function") ? (class2type[ toString.call(obj) ] || "object") : typeof obj; }, // 省略部分程式碼... // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); |
我們先來看看jQuery.each的這部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); //迴圈之後,`class2type`的值是: class2type = { '[object Boolean]' : 'boolean', '[object Number]' : 'number', '[object String]' : 'string', '[object Function]': 'function', '[object Array]' : 'array', '[object Date]' : 'date', '[object RegExp]' : 'regExp', '[object Object]' : 'object', '[object Error]' : 'error' } |
再來看看type方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// type的實現 type: function( obj ) { // 若傳入的是null或undefined,則直接返回這個物件的字串 // 即若傳入的物件obj是undefined,則返回"undefined" if ( obj == null ) { return obj + ""; } // Support: Android<4.0, iOS<6 (functionish RegExp) // 低版本regExp返回function型別;高版本已修正,返回object型別 // 若使用typeof檢測出的obj型別是object或function,則返回class2type的值,否則返回typeof檢測的型別 return (typeof obj === "object" || typeof obj === "function") ? (class2type[ toString.call(obj) ] || "object") : typeof obj; } |
當typeof obj === "object" || typeof obj === "function"時,就返回
class2type[ toString.call(obj)。到這兒,我們就應該明白為什麼Object.prototype.toString.call和$.type那麼像了吧,其實jquery中就是用
Object.prototype.toString.call實現的,把'[object Boolean]'型別轉成'boolean'型別並返回。若class2type儲存的沒有這個變數的型別,那就返回"object"。
除了"object"和"function"型別,其他的型別則使用typeof進行檢測。即number,
string,
boolean型別的變數,使用typeof即可。