## 顏色 1. **RGB** (紅,綠,藍)三種顏色的集合,最低值是0(十六進位00)到最高值255(十六進位FF) 2. **HSL** H色相(0-360),S飽和度(百分比),L亮度(百分比) 3. (不)透明度 **rgba、hsla** (新版瀏覽器可不寫a,直接寫4個值) ## li ...
今天要分享的問題就是:如何在JS中檢查一個變數的類型?
先上結論:
如果判斷的是基本數據類型或JavaScript內置對象,使用toString;如果要判斷的是自定義類型,請使用instanceof。
在 ECMAScript 規範中,共定義了 7 種數據類型,分為 基本類型 和 引用類型 兩大類。
基本類型 也稱為簡單類型,按值訪問。
引用類型 也稱為複雜類型,按址訪問。
JavaScript內置了一些引用類型,如圖所示:
JavaScript的變數是鬆散類型。雖然這使得提供類型信息的方式更加靈活了,但也容易誤用。
下麵來分析常見的四種JavaScript類型檢查方法:typeof
, instanceof
, constructor
, toString
。
typeof
typeof
是一個操作符,其右側跟一個一元表達式,並返回這個表達式的數據類型。
它返回的結果用該類型的字元串(全小寫字母)形式表示。返回值有7種取值:number
、boolean
、symbol
、string
、undefined
、object
和function
。
typeof 3; // number 有效
typeof true; //boolean 有效
typeof Symbol(); // symbol 有效
typeof ''; // string 有效
typeof undefined; //undefined 有效
typeof null; //object 無效
typeof [] ; //object 無效
typeof new Function(); // function 有效
typeof new Date(); //object 無效
typeof new RegExp(); //object 無效
有些時候,typeof
操作符會返回一些令人迷惑但技術上卻正確的值:
- 對於基本類型 ,除
null
以外,均可以返回正確的結果。 - 對於引用類型 ,除
function
以外,一律返回object
類型。 - 對於
null
,返回object
類型。這是一個知名的bug。由於影響範圍越來越大,就沒有修複了。 - 對於
function
函數,返回 function 類型。從技術角度講,函數在ECMAScript中是對象,不是一種數據類型。然而,函數也確實有一些特殊的屬性,因此通過typeof操作符來區分函數和其他對象是有必要的。
由上可以得出:
typeof
對引用類型 操作的返回值不是我們想要的結果。
instanceof
instanceof
是用來判斷 A 是否為 B 的實例的。它的表達式為:A instanceof B
。
如果 A 是 B 的實例,則返回 true,否則返回 false。 在這裡需要特別註意的是:instanceof
斷規則是某個對象的原型鏈是否包含某個構造函數的prototype屬性
。
let arr = [];
arr instanceof Array; // true
arr instanceof Object; // true
看看arr原型鏈簡圖:
arr的 __proto__
直接指向Array.prototype
,間接指向 Object.prototype
,所以按照 instanceof
的判斷規則,[] 就是Array的實例,也是Object的實例。instanceof
返回值都是true
。
依此類推,RegExp
, Object
, Function
也會形成一條對應的原型鏈 。
/abc/ instanceof RegExp // true
({}) instanceof Object // true
(function(){}) instanceof Function // true
instanceof
是通過原型鏈來檢查類型的,所以適用於任何"object"的類型檢查。自定義的類型同樣滿足。
// 比如直接原型關係
function Fruit(){ }
(new Fruit) instanceof Fruit // true
// 原型鏈上的間接原型
function Apple(){}
Apple.prototype = new Fruit
(new Apple) instanceof Fruit // true
註意instanceof
對基本數據類型 不起作用,因為基本數據類型沒有原型鏈。
3 instanceof Number // false
true instanceof Boolean // false
'abc' instanceof String // false
null instanceof String // always false
undefined instanceof String // always false
但你可以這樣:
new Number(3) instanceof Number // true
new Boolean(true) instanceof Boolean // true
new String('abc') instanceof String // true
但這時你已經知道數據類型了,類型檢查已經沒有意義了。
使用constructor屬性
constructor
屬性返回一個指向創建了該對象原型的函數引用。需要註意的是,該屬性的值是那個函數本身。例如:
function Fruit(){}
var a = new Fruit
a.constructor === Fruit // true
constructor不適合用來判斷變數類型。
- 其一,它是一個屬性,非常容易被偽造:
var a = new Fruit
a.constructor === Array
a.constructor === Fruit // false
- 其二,
constructor
指向的是最初創建當前對象的函數,是原型鏈最上層的那個方法:
function Apple(){}
Apple.prototype = new Fruit
function BadApple(){}
BadApple.prototype = new Apple
(new BadApple).constructor === Fruit // true
Fruit.constructor === Function // true
與instanceof類似,constructor只能用於檢測引用對象,對基本數據類型無能為力。
與instanceof不同的是,在訪問基本數據類型的屬性時,JavaScript會自動調用其構造函數來生成一個對象。例如:
(3).constructor === Number // true
true.constructor === Boolean // true
'abc'.constructor === String // true
// 相當於
(new Number(3)).constructor === Number
(new Boolean(true)).constructor === Boolean
(new String('abc')).constructor === String
這種將一個值類型轉換為對象引用類型的機制在其他語言中也存在,稱為裝箱
。
但在基本數據類型中,null
和undefined
調用constructor
會拋出TypeError異常。
null.constructor // TypeError!
undefined.constructor // TypeError!
因為null
是JavaScript原型鏈的起點,undefined
是無效對象,都沒有構造函數,也就不存在constructor
屬性。
instanceof跨視窗問題
我們知道Javascript是運行在宿主環境下的,而每個宿主環境會提供一套ECMA標準的內置對象,以及宿主對象(如window, document),一個新的視窗即是一個新的宿主環境。 不同視窗下的內置對象是不同的實例,擁有不同的記憶體地址。
而instanceof
和constructor
都是通過比較兩個Function是否相等來進行類型判斷的。 此時顯然會出問題,例如:
var iframe = document.createElement('iframe');
var iWindow = iframe.contentWindow;
document.body.appendChild(iframe);
iWindow.Array === Array // false
// 相當於
iWindow.Array === window.Array // false
因此iWindow中的數組arr原型鏈上是沒有window.Array的。請看:
iWindow.document.write('<script> var arr = [1, 2]</script>');
iWindow.arr instanceof Array // false
iWindow.arr instanceof iWindow.Array // true
使用toString方法
使用toString
方法是最為可靠的類型檢測手段,它會將當前對象類型轉換為字元串並輸出。
toString
屬性定義在Object.prototype上,因而所有對象都擁有toString方法。但Array
, Date
等對象會重寫從Object.prototype繼承來的toString,所以最好用Object.prototype.toString來檢測類型。
toString = Object.prototype.toString;
toString.call(new Date); // [object Date]
toString.call(new String);// [object String]
toString.call(Math); // [object Math]
toString.call(3); // [object Number]
toString.call([]); // [object Array]
toString.call({}); // [object Object]
// Since JavaScript 1.8.5
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]
採用toString
也不是完美的,它無法檢測用戶自定義類型。因為Object.prototype是不知道用戶會創造什麼類型的,它只能檢測ECMA標準中的那些內置類型。
toString.call(new Fruit) // [object Object]
因為返回值是字元串,也避免了跨視窗問題。當然IE彈窗中還是有Bug,不必管它了。 現在多少人還在用IE?多少人還在用彈窗?
和Object.prototype.toString類似地,Function.prototype.toString也有類似功能,不過它的this只能是Function,其他類型(例如基本數據類型)都會拋出異常。
其他
有時Duck Typing的類型推斷方式也非常可行,貌似已經成為了前端的慣例。比如jQuery是這樣判斷一個Window的:
isWindow: function(obj){
return obj && typeof obj === 'object' && "setInterval" in obj;
}
總結
typeof
只能檢測基本數據類型,對於null
還有Bug;instanceof
適用於檢測對象,它是基於原型鏈運作的;constructor
指向的是最初創建者,而且容易偽造,不適合做類型判斷;toString
適用於ECMA內置JavaScript類型(包括基本數據類型和內置對象)的判斷;- 引用類型 檢查都有跨視窗問題,比如instanceof和constructor。
總之,如果你要判斷的是基本數據類型或JavaScript內置對象,使用toString; 如果要判斷的是自定義類型,請使用instanceof。