細數判斷資料型別的各種方法
資料型別的分類
要想判斷資料型別,首先要知道資料型別的分類。資料型別分為基本資料型別和引用資料型別。
基本資料型別
基本資料型別有 五 種,ES6中新加了第 六 種基本資料型別——Symbol 型別。
- 數值 (number): 整數和小數。
- 字串 (string): 文字
- 布林值 (boolean):true 和 false 。
- undefined: 表示‘未定義’或不存在。一般情況下變數在聲明後未賦值前都是undefined。
- null: 空值。
- symbol: ES6 引入的新原始資料型別,表示獨一無二的值。
引用資料型別
引用型別資料也會統稱為物件,即廣義的物件,通常除了基本資料型別的其它資料都屬於引用型別資料。
- 物件 (object): 狹義的物件,
{key1:value1, key2:value2,...}
- 陣列 (array):
[value1,value2,...]
- 函式 (function)
- 日期 (date)
- 正則表示式 (RegExp)
- ......
資料型別綜合判斷的各種方法
typeof 運算子
typeof
返回字串,number
、string
、boolean
、symbol
、undefined
、function
,所有其它的引用型別資料都返回 object
,null
也返回 object
。
typeof 666 // "number" typeof 'dora' // "string" typeof true // "boolean" typeof Symbol() // "symbol" typeof undefined // "undefined" typeof null // "object" typeof function(){} // "function" typeof [] // "object" typeof /dora/ // "object"
優點
可利用判斷 undefined
來檢查一個沒有宣告的變數,而不報錯。實際程式設計中,這個特點通常用在判斷語句中。
// 錯誤的寫法
if (v) {
// ...
}
// ReferenceError: v is not defined
// 正確的寫法
if (typeof v === "undefined") {
// 這種寫法在 v 沒有宣告的時候不會報錯。
}
注意
ES6中引入了 let
之後,這個方法也不是萬能的了。當變數在程式碼塊內用 let
宣告的時候,會形成“暫時性死區”(temporal dead zone,簡稱 TDZ),此時這個方法就沒用了,typeof 還是會報錯。
typeof x; // ReferenceError
let x;
缺點
不能準確的判斷引用型別資料的具體型別,除了函式外,其餘的都是返回object
。
typeof {} // "object"
typeof [] // "object"
此時,在需要判斷陣列或者物件時,就不適用了。
Object.prototype.toString.call(value)
Object.prototype.toString()
方法返回物件的型別字串,因此可以用來判斷一個值的型別。
var obj = {};
obj.toString() // "[object Object]"
上面程式碼呼叫空物件的toString方法,結果返回一個字串 object Object
,其中第二個Object表示該值的 建構函式。
由於例項物件可能會自定義toString方法,覆蓋掉 Object.prototype.toString
方法,所以為了得到型別字串,最好直接使用Object.prototype.toString
方法。通過函式的call
方法,可以在任意值上呼叫這個方法,幫助我們判斷這個值的型別。
Object.prototype.toString.call(value)
上面程式碼表示對value這個值呼叫Object.prototype.toString
方法。
返回值
不同資料型別的Object.prototype.toString
方法返回值如下:
- 數值:返回
[object Number]
。 - 字串:返回
[object String]
。 - 布林值:返回
[object Boolean]
。 - undefined:返回
[object Undefined]
。 - null:返回
[object Null]
。 - Symbol型別:返回
[object Symbol]
。 - 陣列:返回
[object Array]
。 - arguments 物件:返回
[object Arguments]
。 - 函式:返回
[object Function]
。 - Error 物件:返回
[object Error]
。 - Date 物件:返回
[object Date]
。 - RegExp 物件:返回
[object RegExp]
。 - 其他物件:返回
[object Object]
。
Object.prototype.toString.call(2) // "[object Number]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(Symbol()) // "[object Symbol]"
Object.prototype.toString.call(Math) // "[object Math]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"
封裝實用函式
利用這個特性,可以封裝一個比typeof
運算子更準確的型別判斷函式。
var type = function (o){
var s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
type({}); // "object"
type([]); // "array"
type(5); // "number"
type(null); // "null"
type(); // "undefined"
type(/dora/); // "regexp"
type(new Date()); // "date"
在上面這個type函式的基礎上,還可以加上專門判斷某種型別資料的方法。
var dataArr = ['Null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp'];
dataArr.forEach(function (t) {
type['is' + t] = function (o) {
return type(o) === t.toLowerCase();
};
});
type.isObject({}); // true
type.isNumber(NaN); // true
type.isRegExp(/abc/); // true
instanceof 運算子
instanceof
運算子返回一個布林值,表示物件是否為某個建構函式的例項。
function People(){}
var person = new People();
person instanceof People // true
判斷原理
遍訪物件的原型鏈上的每個原型物件,如果遍訪到這個原型物件,是某個建構函式的prototype
,那麼就認為物件是這個建構函式的例項,返回true。因此同一個例項物件,可能會對多個建構函式都返回true,因為繼承的子類例項也是父類的例項。
var d = new Date();
d instanceof Date // true
d instanceof Object // true
特殊情況
有一種特殊情況,就是左邊物件的原型鏈上,只有null物件。這時,instanceof
判斷會失真。
var obj = Object.create(null);
typeof obj // "object"
obj instanceof Object // false
上面程式碼中,Object.create(null)
返回一個新物件obj
,它的原型是null
。右邊的建構函式Object
的prototype
屬性,不在左邊的原型鏈上,因此instanceof
就認為obj
不是Object
的例項。
只要一個物件的原型不是null
,instanceof
運算子的判斷就不會失真。
型別判斷
instanceof
運算子只能用於物件,不適用原始型別的值,且對於undefined
和null
,instanceof
運算子總是返回false
。
'hello' instanceof String // false
undefined instanceof Object // false
null instanceof Object // false
可用於物件,無論是 JavaScript 內建物件或是自定義建構函式生成的物件,都可進行判斷。
[] instanceof Array // true
({}) instanceof Object // true
(function(){}) instanceof Function // true
/a/ instanceof RegExp // true
new Date() instanceof Date // true
person instanceof People // true
constructor 屬性
prototype
物件有一個constructor
屬性,預設指向prototype
物件所在的建構函式。由於constructor
屬性定義在prototype
物件上面,意味著可以被所有例項物件繼承。因此,正常情況下,所有物件例項都有一個constructor
屬性,屬性值指向構造此物件例項的建構函式。
[].constructor === Array // true
[].constructor === Object // false
window.constructor === Window //true
name屬性
如果不能確定物件例項的constructor
屬性是什麼函式,可通過函式的name
屬性,從例項得到建構函式的名稱。
function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"
型別判斷
基本資料型別
null
和undefined
是無效的物件,因此是不會有constructor
存在的,這兩種型別的資料需要通過typeof
來判斷。
number
、string
、boolean
三種資料型別有對應的Number
、String
、Boolean
三個原生物件(包裝物件)。因此,也可用 constructor
進行判斷。symbol
型別也可判斷。
(333).constructor.name // "Number"
''.constructor.name // "String"
false.constructor.name // "Boolean"
Symbol().constructor.name // "Symbol"
引用資料型別
JavaScript 內建物件或是自定義建構函式生成的物件,都可進行判斷。
new Date().constructor === Date //true
[].constructor === Array //true
function F(){};
var f = new F();
f.constructor === F // true
f.constructor === Object // false
不穩定因素
constructor
屬性表示原型物件與建構函式之間的關聯關係,有時開發者會因業務關係重寫prototype
,原有的constructor
會丟失,若沒有同時修改constructor
屬性,引用的時候就會出錯,constructor
會預設為Object
。
function Person(name) {
this.name = name;
}
Person.prototype.constructor === Person // true
Person.prototype = {
method: function () {}
};
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true
因此,修改原型物件時,一般要同時修改constructor
屬性的指向,或者只在原型物件上新增方法,不要重寫prototype
。
總結
typeoftypeof
可用來判斷基本資料型別和函式,不可以對引用資料型別進行具體的判斷。
Object.prototype.toString.call(value)Object.prototype.toString.call(value)
可用於判斷多種資料型別:基本資料型別和 JavaScript 內建物件,然而對於一些自定義建構函式生成的物件就不能進行判斷了。
instanceofinstanceof
運算子不適用判斷原始型別的值,只能用於判斷物件,無論是 JavaScript 內建物件或是自定義建構函式生成的物件,都可進行判斷。然而由於繼承的存在,instanceof
判斷也不完全準確,只能用來判斷兩個物件是否屬於原型鏈的關係,而不一定能獲取物件的具體型別。
constructorconstructor
屬性可準確的判斷物件例項是由哪個建構函式生成的,但自定義建構函式生成的物件,往往會因為重寫prototype
造成constructor
屬性指向不準確,因此使用的時候也要注意一下。
一些其它的具體型別判斷
判斷變數是否為物件(引用型別)
Object(x)
的引數為物件時,總是返回該物件,不做轉換;當引數為原始型別時,會轉換為對應的包裝物件的例項,引數為空或者undefined
或者null
時,返回一個空物件。
function isObject(value) {
return value === Object(value);
}
isObject([]); // true
isObject(true); // false
判斷是不是 NaN
所有資料型別中,只有NaN
不等於它本身
function isNaN(value) {
return value !== value;
}
isNaN(NaN); // true
判斷陣列的方法 Array.isArray()
除了上文提到的三種方法(toString()
、instanceof
、constructor
)可判斷外,還有一個Array
建構函式自帶的方法isArray()
可判斷。
Array.isArray(x)
如果x
是陣列,則為true
; 否則為false
。
Array.isArray([]); // true
Array.isArray(new Array()); // true
Array.isArray(Array.prototype); // true 鮮為人知的事實:其實 Array.prototype 也是一個數組。
使用之前需檢測一下相容性,對於不相容的瀏覽器可使用下面的程式碼建立該方法。
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}