了解到了JavaScript的變量主要有基本類型(undefined、null城须、boolean扒吁、number和string, ES6中還新增了Symbol)和引用類型(對象稳摄、數(shù)組、函數(shù))泼橘。但在JavaScript中用戶定義的類型(object)并沒有類的聲明涝动,因此繼承關(guān)系只能通過構(gòu)造函數(shù)和原型鏈接來檢查。而在這篇文章中炬灭,主要整理了在JavaScript中如何檢測一個(gè)變量的類型醋粟。
在JavaScript中常見的類型檢查手段主要有:typeof、instanceof重归、constructor和toString幾種米愿。接下來主要看看這幾種類型檢查手段的使用與區(qū)別之處。
1:type of(操作符返回的是字符串提前,它的返回值如下)
Undefined>>>>Undefined Null>>>"object"
布爾值>>>>"boolean" 數(shù)值 >>>"number"
字符串>>>"string" Symbol>>>"Symbol"
函數(shù)對象>>>"function" 任何其他對象>>"object"
宿主對象(JS環(huán)境提供的吗货,比如瀏覽器) >>>> Implementation-dependent
typeof ["w3cplus","大漠"];
typeof new Date();
typeof new String("w3cplus");
typeof new function (){};
typeof /test/i; 返回的都是object
另外對于Null,typeof檢測返回的值也是一個(gè)object:
這是typeof的一個(gè)知名Bug狈网。先忽略其是不是typeof的bug宙搬,在JavaScript中笨腥,null也是基本數(shù)據(jù)類型之一,它的類型顯然是Null勇垛。其實(shí)這也反映了null的語義脖母,它是一個(gè)空指針表示對象為空,而undefined才表示什么都沒有
根據(jù)上面的內(nèi)容闲孤,簡單的對typeof做一個(gè)歸納:
typeof只能檢測基本數(shù)據(jù)類型谆级,對于null還有一個(gè)Bug。
在實(shí)際開發(fā)中使用typeof時(shí)需要養(yǎng)成一個(gè)好的習(xí)慣讼积。比如肥照,使用typeof一個(gè)較好的習(xí)慣是寫一個(gè)多種狀態(tài)的函數(shù):
function f (x) { if (typeof x == "function") { ... // 當(dāng)x是一個(gè)函數(shù)時(shí),做些什么... } else { ... // 其它狀態(tài) } }
勤众,前面使用typeof 對一個(gè)數(shù)組做檢測的時(shí)候也返回object
那么在JavaScrit中舆绎,可以通過創(chuàng)建一個(gè)函數(shù),并且通過一些正則表達(dá)式们颜,讓這個(gè)函數(shù)實(shí)現(xiàn)一個(gè)改進(jìn)版本的typeof吕朵。如下所示:
function toType (obj) {
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
}
之后我們做測試,則如下:
toType({name: "大漠"}); // => "object"
toType(["W3cplus","大漠"]); // => "array"
(function() {console.log(toType(arguments))})(); // => arguments
toType(new ReferenceError); // => "error"
toType(new Date); // => "date"
toType(/a-z/); // => "regexp"
toType(Math); // => "math"
toType(JSON); // => "json"
toType(new Number(4)); // => "number"
toType(new String("abc")); // => "string"
toType(new Boolean(true)); // => "boolean"
toType(function foo() {console.log("Test")}); // =>"function"
2:instanceof(操作符用于檢測某個(gè)對象的原型鏈?zhǔn)欠癜硞€(gè)構(gòu)造函數(shù)的prototype屬性)
function C(){}
function D(){}
var o = new C();
// true窥突,因?yàn)?Object.getPrototypeOf(o) === C.prototype
o instanceof C;
// false努溃,因?yàn)?D.prototype不在o的原型鏈上
o instanceof D;o對象的原型鏈上有很多對象(成為隱式原型),比如o.proto阻问,o.proto.proto等等梧税。因?yàn)?Object.getPrototypeOf(o) === C.prototype所以返回的是true,而D.prototype不在o的原型鏈上,所以返回的是false。
需要注意的是则拷,如果表達(dá)式 o instanceof C 返回true贡蓖,則并不意味著該表達(dá)式會(huì)永遠(yuǎn)返回ture,因?yàn)镃.prototype屬性的值有可能會(huì)改變煌茬,改變之后的值很有可能不存在于o的原型鏈上斥铺,這時(shí)原表達(dá)式的值就會(huì)成為false。另外一種情況下坛善,原表達(dá)式的值也會(huì)改變晾蜘,就是改變對象o的原型鏈的情況,雖然在目前的ES規(guī)范中眠屎,我們只能讀取對象的原型而不能改變它剔交,但借助于非標(biāo)準(zhǔn)的proto魔法屬性,是可以實(shí)現(xiàn)的改衩。比如執(zhí)行o.proto = {}之后岖常,o instanceof C就會(huì)返回false了。
instanceof是通過原型鏈來檢查類型的葫督。所謂的“類型”是一個(gè)構(gòu)
造函數(shù)竭鞍。例如:
// 比如直接原型關(guān)系
function Animal () {};
var a = new Animal();
a instanceof Animal; // => true
// 原型鏈上的間接原型
function Cat() {};
Cat.prototype = new Animal;
var b = new Cat();
b instanceof Animal; // =>
instanceof除了適用于任何object的類型檢查之外板惑,也可以用來檢測內(nèi)置兌現(xiàn),比如:Array偎快、RegExp冯乘、Object、Function:
[1, 2, 3] instanceof Array // true
/abc/ instanceof RegExp // true
({}) instanceof Object // true
instanceof對基本數(shù)據(jù)類型檢測不起作用晒夹,主要是因?yàn)榛緮?shù)據(jù)類型沒有原型
但是還可以用的:如下:
new Number(3) instanceof Number // true
new Boolean(true) instanceof Boolean // true
new String('abc') instanceof String // true
不過這個(gè)時(shí)候裆馒,都知道數(shù)據(jù)類型了,再使用instanceof來做檢測就毫無意義了丐怯。
簡單總結(jié)一下
instanceof適用于檢測對象喷好,它是基于原型鏈運(yùn)作的。
3:constructor(返回一個(gè)指向創(chuàng)建了該對象原型的函數(shù)引用读跷。需要注意的是绒窑,該屬性的值是那個(gè)函數(shù)本身)
function Animal () {};
var a = new Animal;
a.constructor === Animal; // => true
constructor不適合用來判斷變量類型。首先因?yàn)樗且粋€(gè)屬性舔亭,所以非常容易偽造:
var a = new Animal;
a.constructor === Array;
a.constructor === Animal; // => false
另外constructor指向的是最初創(chuàng)建當(dāng)前對象的函數(shù),是原型鏈最上層的那個(gè)方法:
function Cat () { };
Cat.prototype = new Animal;
function BadCat () { };
BadCat.prototype = new Cat;
var a = new BadCat;
a.constructor === Animal; // => true
Animal.constructor === Function; // => true
與instanceof類似蟀俊,constructor只能用于檢測對象钦铺,對基本數(shù)據(jù)類型無能為力。而且因?yàn)閏onstructor是對象屬性肢预,在基本數(shù)據(jù)類型上調(diào)用會(huì)拋出TypeError異常:
null.constructor; // => TypeError
undefined.constructor; // => TypeError
和instanceof不同的是矛洞,在訪問基本數(shù)據(jù)類型的屬性時(shí),JavaScript會(huì)自動(dòng)調(diào)用其構(gòu)造函數(shù)來生成一個(gè)對象烫映,如:
.constructor === Number // true
true.constructor === Boolean // true
'abc'.constructor === String // true
// 相當(dāng)于
(new Number(3)).constructor === Number
(new Boolean(true)).constructor === Boolean
(new String('abc')).constructor === String
另外沼本,使用constructor有兩個(gè)問題。第一個(gè)問題它不會(huì)走原型鏈:
function Animal () {};
function Cat () {};
Cat.prototype = new Animal;
Cat.prototype.constructor = Cat;
var felix = new Cat;
felix.constructor === Cat; // => true
felix.constructor === Animal; // => false
就是null和undefined使用constructor會(huì)報(bào)異常
同樣對constructor做一個(gè)簡單的總結(jié)
constructor指向的是最初創(chuàng)建者锭沟,而且易于偽造抽兆,不適合做類型判斷。
跨窗口問題
JavaScript是運(yùn)行在宿主環(huán)境下的族淮,而每個(gè)宿主環(huán)境都會(huì)提供一套標(biāo)準(zhǔn)的內(nèi)置對象辫红,以及宿主對象(如window,document),一個(gè)新的窗口即是一個(gè)新的宿主環(huán)境祝辣。不同的窗口下的內(nèi)置對象是不同的實(shí)例贴妻,擁有不同的內(nèi)存地址。
而instanceof和constructor都是通過比較兩個(gè)Function是否相等來進(jìn)行類型判斷的蝙斜。 此時(shí)顯然會(huì)出問題名惩,例如:
var iframe = document.createElement('iframe');
var iWindow = iframe.contentWindow;
document.body.appendChild(iframe);
iWindow.Array === Array // false
// 相當(dāng)于
iWindow.Array === window.Array // false
因此iWindow中的數(shù)組arr原型鏈上是沒有window.Array的
toString
最簡單的數(shù)據(jù)類型檢測方法應(yīng)當(dāng)算是toString,不過其看起來像是一個(gè)黑魔法:
toString屬性定義在Object.prototype上孕荠,因而所有對象都擁有toString方法娩鹉。默認(rèn)情況之下攻谁,調(diào)用{}.toString()(一個(gè)object),將會(huì)得到[object object]底循。
我們可以通過.call()來改變這種情況(因?yàn)樗鼘⑵鋮?shù)轉(zhuǎn)換為值類型)巢株。例如,通過使用.call(/test/i)(正則表達(dá)多),這個(gè)時(shí)候[object object]將變成[object RegExp]熙涤。
Object.prototype.toString.call([]); // => [object Array]
Object.prototype.toString.call({}); // => [object Object]
Object.prototype.toString.call(''); // => [object String]
Object.prototype.toString.call(new Date()); // => [object Date]
Object.prototype.toString.call(1); // => [object Number]
Object.prototype.toString.call(function () {}); // => [object Function]
Object.prototype.toString.call(/test/i); // => [object RegExp]
Object.prototype.toString.call(true); // => [object Boolean]
Object.prototype.toString.call(null); // => [object Null]
Object.prototype.toString.call(); // => [object Undefined]
不過toString也不是十全十美的阁苞,因?yàn)樗鼰o法檢測用戶自定義類型。主要是因?yàn)镺bject.prototype是不知道用戶會(huì)創(chuàng)造什么類型的祠挫,它只能檢測ECMA標(biāo)準(zhǔn)中的那些內(nèi)置類型那槽。
function Animal () {};
Object.prototype.toString.call (Animal); // => [object Function]
Object.prototype.toString.call (new Animal); // => [object Object]
和Object.prototype.toString類似,F(xiàn)unction.prototype.toString也有類似功能等舔,不過它的this只能是Function骚灸,其它類型(如基本數(shù)據(jù)類型)都會(huì)拋出異常。
自定義檢測數(shù)據(jù)類型的函數(shù)
通過前面的內(nèi)容介紹慌植,我們可以獲知:
typeof只能檢測基本數(shù)據(jù)類型甚牲,對于null還有Bug;
instanceof適用于檢測對象蝶柿,它是基于原型鏈運(yùn)作的丈钙;
constructor指向的是最初創(chuàng)建者,而且容易偽造交汤,不適合做類型判斷雏赦;
toString適用于ECMA內(nèi)置JavaScript類型(包括基本數(shù)據(jù)類型和內(nèi)置對象)的類型判斷;
基于引用判等的類型檢查都有跨窗口問題芙扎,比如instanceof和constructor星岗。總之,如果你要判斷的是基本數(shù)據(jù)類型或JavaScript內(nèi)置對象戒洼,使用toString俏橘; 如果要判斷的是自定義類型,請使用instanceof圈浇。
其實(shí)敷矫,為了便于使用,可以在toString的基礎(chǔ)上封閉一個(gè)函數(shù)汉额。比如@toddmotto寫的axis.js:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.axis = factory();
}
}(this, function () {
'use strict';
var axis = {};
var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');
function type() {
return Object.prototype.toString.call(this).slice(8, -1);
}
for (var i = types.length; i--;) {
axis['is' + types[i]] = (function (self) {
return function (elem) {
return type.call(elem) === self;
};
})(types[i]);
}
return axis;
}));
有了這個(gè)函數(shù)曹仗,咱們只需要像下面這樣使用,就可以檢測數(shù)據(jù)類型:
axis.isArray([]); // true
axis.isObject({}); // true
axis.isString(''); // true
axis.isDate(new Date()); // true
axis.isRegExp(/test/i); // true
axis.isFunction(function () {}); // true
axis.isBoolean(true); // true
axis.isNumber(1); // true
axis.isNull(null); // true
axis.isUndefined(); // true
總結(jié)
typeof instanceof constructor toString
避免字符串比較 No Yes Yes No
常用的 Yes Yes No No
檢查自定義類 No Yes Yes No
直接檢查null No No No Yes
直接檢查undefined Yes No No Yes
跨窗口工作 Yes No No Yes
我們總結(jié)為一句口訣:如果你要判斷的是基本數(shù)據(jù)類型或JavaScript內(nèi)置對象蠕搜,使用toString怎茫; 如果要判斷的時(shí)自定義類型,請使用instanceof。