一、前言
類型判斷有時候真的頭疼,但是一旦熟練使用就會覺得不過如此。初級的,會判斷數字和字符串。中級的,會判斷數組和對象。進階的,會判斷日期,正則,錯誤類型。高級的,會判斷plainObject,空對象,window對象等等。
基本類型:String、Number、Boolean、Symbol、Undefined、Null
引用類型:Object
基本類型也稱爲簡單類型,由於其佔據空間固定,是簡單的數據段,爲了便於提升變量查詢速度,將其存儲在棧中,即按值訪問。
引用類型也稱爲複雜類型,由於其值的大小會改變,所以不能將其存放在棧中,否則會降低變量查詢速度,因此,其值存儲在堆(heap)中,而存儲在變量處的值,是一個指針,指向存儲對象的內存處,即按址訪問。引用類型除 Object 外,還包括 Function 、Array、RegExp、Date 等等。
鑑於 ECMAScript 是鬆散類型的,因此需要有一種手段來檢測給定變量的數據類型。對於這個問題,JavaScript 也提供了多種方法,但遺憾的是,不同的方法得到的結果參差不齊。
二、typeof
typeof是最經常用到的判斷類型的。
typeof('saucxs') //'string'
typeof 'saucxs' //'string'
typeof function(){console.log('saucxs')} //'function'
typeof ['saucxs','songEagle',1,2,'a'] //'object'
typeof {name: 'saucxs'} //'object'
typeof 1 //'number'
typeof undefined //'undefined'
typeof null //'object'
typeof /^\d/ //'object'
typeof Symbol // 'function'
其實,typeof是一個運算符,和加減乘除類似,這就是爲啥可以這樣寫 typeof 'saucxs'。
在《JavaScript權威指南》中對typeof的介紹:typeof是一元操作符,放在單個操作數的前面,操作數可以是任意類型。返回值表示操作數的類型的一個字符串。
JavaScript中一共有6中基本數據類型:string,number,boolean,null,undefined,symbol,一種對象類型:object。
分別對應的typeof的值,結果不是一一對應的,分別:string,number,boolean,object,undefined,function,對象類型:object。
string | number | boolean | null | undefined | symbol | object |
string | number | boolean | object | undefined | function | object |
注意:typeof 可以檢測函數類型
但是在object下還有很多細分內部屬性:Array,Function,Date,RegExp,Error等。
var date = new Date();
var error = new Error();
console.log(typeof date); // object
console.log(typeof error); // object
所以還需要更好的區分。
三、instanceof
使用instanceof的前提條件:object instanceof constructor。object--
要檢測的對象。constructor--某個構造函數。說明使用這個instanceof必須是用來檢測對象的的類型,不能檢測其他類型。
A instanceof B用來判斷A是否爲B的實例。如果A是B的實例,則返回true,否則false。
原理:instanceof是檢測原型。
instanceof (a,B) = {
var l = a.__proto__;
var R = B.prototype;
if(l === R) {
// a的內部屬性 __proto__ 指向 B 的原型對象
return true;
}
return false;
}
分析:a的_proto_指向B的prototype時,a就是B的實例。
[] instanceof Array //true
[] instanceof Object //true
new Array([1,43,6]) instanceof Array // true
new Array([1,43,6]) instanceof Object // true
{} instanceof Object // 原型上沒有定義 Uncaught SyntaxError: Unexpected token instanceof
({}) instanceof Object; //true
Object.create({'name': 'saucxs'}) instanceof Object //true
Object.create(null) instanceof Object //false 一種創建對象的方法,這種方法創建的對象不是Object的一個實例
new Date() instanceof Date //true
new Date() instanceof Object //true
'saucxs' instanceof Object //false
'saucxs' instanceof String //false
new String("saucxs") instanceof Object //true
new String("saucxs") instanceof String //true
1 instanceof Object //false
1 instanceof Number //false
new Number(1) instanceof Object //true
new Number(1) instanceof Number //true
true instanceof Object //false
true instanceof Boolean //false
new Boolean(true) instanceof Object //true
new Boolean(true) instanceof Boolean //true
null instanceof Object //false
undefined instanceof Object //false
Symbol() instanceof Symbol //false
注意:1、new
Date
對象既屬於Object,又屬於Date。(他們是由Object
類派生出來的)。
2、用字面量創建的數組或者構造函數創建的數組,既屬於Object,又屬於Array。
3、用對象字面量創建的對象object 會報錯, {} instanceof Object;使用構造函數創建的對象屬於Object。
4、用字面量的創建的字符串,數字,布爾,既不屬於Object,也不屬於各自類型;只有使用構造函數創建的字符串,數字,布爾,既屬於Object,又屬於各自的類型。
發現[],構造函數創建的Date,Object,String,Number,Boolean。既屬於自身,又屬於Object。
舉個例子,[], 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 只能判斷兩個對象是否屬於實例關係, 而不能判斷一個對象實例具體屬於哪種類型。
存在的問題:
它假定只有一個全局執行環境。如果網頁中包含多個框架,那實際上就存在兩個以上不同的全局執行環境,從而存在兩個以上不同版本的構造函數。如果你從一個框架向另一個框架傳入一個數組,那麼傳入的數組與在第二個框架中原生創建的數組分別具有各自不同的構造函數。
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[0].Array;
var arr = new xArray(1,2,3); // [1,2,3]
arr instanceof Array; // false
針對數組問題,ES5 提供了 Array.isArray() 方法 。該方法用以確認某個對象本身是否爲 Array 類型。
if (Array.isArray(value)){
//對數組執行某些操作
}
Array.isArray() 本質上檢測的是對象的 [[Class]] 值,[[Class]] 是對象的一個內部屬性,裏面包含了對象的類型信息,其格式爲 [object Xxx] ,Xxx 就是對應的具體類型 。對於數組而言,[[Class]] 的值就是 [object Array] 。
四、constructor
定義一個構造函數Func(),JS引擎會給Func添加prototype原型,然後再給prototype上添加一個constructor屬性,並且指向Func的引用。
實例化一個函數func,var func = new Func()。此時Func原型上的constructor傳遞到func上,因此func.constructor === Func。
Func利用原型對象上的constructor引用自身,當Func作爲構造函數來創建實例化對象時,原型上的constructor就會遺傳到新創建的對象上。從原型鏈角度講,構造函數Func就是新對象的func的類型。這樣存在的意義就是新對象產生之後,可以追蹤數據類型。
JavaScript 中的內置對象在內部構建時也是這樣做的:
'saucxs'.constructor === String //true
new String('saucxs').constructor === String //true
[].constructor === Array //true
new Array([12,56]).constructor === Array //true
new Number(12).constructor === Number //true
new Function(console.log('saucxs')).constructor === Function //true
new Date().constructor === Date //true
new Error().constructor === Error //true
window.constructor === Window //true
document.constructor === HTMLDocument //true
注意:
(1) null 和 undefined 是無效的對象,因此是不會有 constructor 存在的,這兩種類型的數據需要通過其他方式來判斷。
(2)函數的 constructor 是不穩定的,這個主要體現在自定義對象上,當開發者重寫 prototype 後,原有的 constructor 引用會丟失,constructor 會默認爲 Object
爲什麼變成了 Object?
因爲 prototype 被重新賦值的是一個 { }, { } 是 new Object() 的字面量,因此 new Object() 會將 Object 原型上的 constructor 傳遞給 { },也就是 Object 本身。
因此,爲了規範開發,在重寫對象原型時一般都需要重新給 constructor 賦值,以保證對象實例的類型不被篡改。
五、Object.prototype.toString
toString() 是 Object 的原型方法,調用該方法,默認返回當前對象的 [[Class]] 。這是一個內部屬性,其格式爲字符串 [object xxx] ,其中 xxx 就是對象的類型。
這個方法到底是個啥?可以 先看ES5 規範地址:https://es5.github.io/#x15.2.4.2
toString方法被調用的時候,會按照這個步驟執行:
(1)如果this的值是undefined,就返回[object Undefined];
(2)如果this的值是null,就返回[object Null];
(3)讓O成爲ToObject(this)的結果;
(4)讓class成爲O的內部屬性[[class]]的值;
(5)最後返回由"[object" 和 class 和 "]"三個部分組成的字符串。
一句話就是:調用Object.prototype.toString 會返回一個"[object" 和 class 和 "]"組成的字符串,而class要判斷對象的內部屬性。
console.log(Object.prototype.toString.call(undefined)) // '[object Undefined]'
console.log(Object.prototype.toString.call(null)) // '[object Null]'
console.log(Object.prototype.toString.call(Window)) // '[object Function]'
var date = new Date(); console.log(Object.prototype.toString.call(date)) // '[object Date]'
console.log(Object.prototype.toString.call(Symbol)) // '[object Function]'
注意:通過call改變this的指向。
所以這個class的值是識別對象類型的關鍵,所以使用Object.prototype.toString方法識別出更多的類型,可以識別出至少11種類型
var number = 1; // [object Number]
var string = '123'; // [object String]
var boolean = true; // [object Boolean]
var und = undefined; // [object Undefined]
var nul = null; // [object Null]
var obj = {a: 1} // [object Object]
var array = [1, 2, 3]; // [object Array]
var date = new Date(); // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g; // [object RegExp]
var func = function a(){}; // [object Function]
Math //[object Math]
JSON //[object JSON]
注意:
1、其實Math對象和JSON對象,並不會去判斷;
2、Math對象並不像Date和String那樣對象的類,沒有構造函數Math(), Math.sin()這樣的只是函數,不是某一個對象的方法。
六、研究jquery的type API
使用Object.prototype.toString這個方法,判斷各種類型就比較輕鬆了,參考了jquery的源碼的type部分:
function type(obj) {
// 一箭雙鵰
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[Object.prototype.toString.call(obj)] || "object" :
typeof obj;
}
其中class2type部分
var class2type = {};
// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
class2type["[object " + item + "]"] = item.toLowerCase();
})
使用:
type(1); //'number'
type('123456'); //'string'
type(true); //boolean
type(undefined); //undefined
type(null); //'null'
type({name: 'saucxs'}); //'object'
type([1,2,'saucxs',3,4]); //'array'
type(new Date()); // 'date'
type(new Error()); //'error'
type(/^\d/); //'regexp'
type(function(){console.log('saucxs')}); //'function'
type(Symbol); //'function'
這就非常完美的實現了,對我們日常需要類型的判斷。
實現了判斷日期,正則,錯誤類型等。
如果還需要判斷比較複雜的,比如:空對象,window對象,類數組對象等等。
七、空對象EmptyObject
jQuery提供了 isEmptyObject 方法來判斷是否是空對象
function isEmptyObject( obj ) {
var name;
for ( name in obj ) {
return false;
}
return true;
}
思路:判斷空對象就是判斷是是否有屬性值,for循環一旦執行,就說明有屬性,有屬性返回false。
console.log(isEmptyObject({})); // true
console.log(isEmptyObject([])); // true
console.log(isEmptyObject(null)); // true
console.log(isEmptyObject(undefined)); // true
console.log(isEmptyObject(123)); // true
console.log(isEmptyObject('')); // true
console.log(isEmptyObject(true)); // true
這個判斷主要用來區別 {} 和 {name: 'saucxs'} 就行。
注意點:(1)for in 是ES6的屬性,這個會遍歷原型上的屬性。(2)使用Object.keys(obj)是ES5的屬性,不會遍歷原型上的屬性
八、window對象
window對象是客戶端js的全局對象,他有一個window屬性指向自身。根據這個特性判斷是否爲window對象。
function isWindow(obj){
return obj != null && obj ===obj.window;
}
注意:一個普通對象擁有 window
屬性,並且指向自身。比如這個:
function isWindow( obj ) {
return obj != null && obj === obj.window;
}
let fakeWindow = {}
fakeWindow.window = fakeWindow
isWindow(fakeWindow) // true
是不是可以這麼修改呢?
function isWindow(obj) {
return !!(window && obj === window)
}
九、類數組對象
如果對類數組沒有概念,舉個例子:
1、數組和類數組
var arr = [,,3];
對應的類數組是
var arrLike = {
2: 3,
length: 3
}
看jquery的源碼
function isArrayLike(obj) {
// obj 必須有 length屬性
var length = !!obj && "length" in obj && obj.length;
var typeRes = type(obj);
// 排除掉函數和 Window 對象
if (typeRes === "function" || isWindow(obj)) {
return false;
}
return typeRes === "array" || length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj;
}
所以如果 isArrayLike 返回true,至少要滿足三個條件之一:
(1)是數組
(2)長度爲 0
(3)lengths 屬性是大於 0 的數字類型,並且obj[length - 1]必須存在
第三個條件:數組中用逗號直接跳過的時候,我們認爲該元素是不存在的,類數組對象中也就不用寫這個元素,但是最後一個元素是一定要寫的,要不然 length 的長度就不會是最後一個元素的 key 值加 1。比如數組可以這樣寫
var arr = [1,,];
console.log(arr.length) // 2
改寫成類數組
var arrLike = {
0: 1,
length: 1
}
所以符合條件的類數組對象是一定存在最後一個元素的!
十、判斷是不是dom元素
isElement 判斷是不是 DOM 元素。
isElement = function(obj) {
return !!(obj && obj.nodeType === 1);
};
十一、總結
判斷類型主要時四個方法:(1)typeof;(2)instanceof;(3)constructor;(4)Object.prototype.toString()。
從基本的六種類型判斷,可以使用typeof,如果涉及到對象的內部類型時候;還可以使用instanceof,檢測對象原型的;還可以使用constructor屬性是不是指向他們構造函數;需要使用Object.prototype.toString(),如果需要判斷空對象,可以使用ES6 的 for in 來判斷,用window屬性指向自身來判斷是不是window對象。以及類數組對象的判斷,以及判斷是不是dom元素的判斷。