JavaScript是弱类型的,很多情况下我们需要做类型检测.然而做类型检测有很多方法,但是这些类型检测都有自己的局限性,这里我们将介绍一下JavaScript中的类型检测方法,并指出这些类型检测方法的优点和缺点.

outline

  1. typeof
  2. instanceof/constructor
  3. duck-typing
  4. Object.prototype.toString

typeof做类型检测

typeof方法返回变量类型,比如:

1
2
3
4
5
6
7
8
9
//基本类型检测
typeof 3 // "number"
typeof "abc" // "string"
typeof true // "boolean"
typeof undefined // "undefined"

//对象类型检测
typeof {} // "object"
typeof function(){} // "function"

这一切看起来都十分美好,直到出现了null:

1
typeof null //object

在往下看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typeof new String("1") // "object"
typeof new Number(1) // "object"
typeof new Date() // "object"
typeof new Boolean(true)
typeof /x/ // "object"
typeof [1,2,4] // "object"

//用户自定义类型
var A = function(){
this.name = "A";
}
var a = new A();
typeof a // "object"

//DOM
var div = document.getElementById("div");
typeof div // "object"

法克,都是object!!!!从上面我们可以看出:

  1. 对于基本类型(string,number,boolean,null,undefined),除了null外(typeof对于null会误判为object),typeof都能返回正确结果.
  2. 对于Object类型(非基本类型),typeof对于Function类型有较好的效果,除此之外,typeof无法区分String、Number、Date、Boolean、RegExp、Array、用户自定义类型、DOM元素等(返回都是object)
  3. typeof可以区分Object类型和基本类型(除了null).

instanceof/constructor做类型检测

object instanceof constructor
js会在object的原型链上查找是否存在constructor的原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function A(){
this.showA = function(){
console.log('A function');
}
}
function B(){
this.showB = function(){
console.log('B function');
}
}
A.prototype = new B();
var a = new A();

a.__proto__ === A.prototype
//true 都是B{}

a.__proto__.__proto__ === B.prototype
//true 都是Object{constructor: function B(){},__proto__:Object}

a.__proto__.__proto__.__proto__ === Object.prototype
//true 都是Object

a.__proto__.__proto__.__proto__.__proto__ === null
//true


a instanceof A //true 因为a.__proto__ === A.prototype
a instanceof B //true 因为a.__proto__.__proto__ === B.prototype
a instanceof Object //true 因为a.__proto__.__proto__.__proto__ === Object.prototype

constructor做类型检测,大致就像下面这样:

1
2
3
function A(){}
var a = new A();
a.constructor === A//true

但是用constructor的方式检测有两点不好:
1.constructor不会遍历原型链(相对于instanceof)

1
2
3
4
5
6
7
8
9
10
11
function A(){}
function B(){}
B.prototype = new A()
B.prototype.constructor = B
var b = new B()

b instanceof B//true
b instanceof A//true

b.constructor === B//true
b.constructor === A//false

2.constructor对于null、undefined会出现TypeError的问题
所以说constructor检测还不如instanceof检测来的好,但是instanceof对于基本类型的检测就不太友好了(除了null和undefined,因为null和undefined不是任何对象的实例,所以检测结果一直是false),比如:

1
2
3
4
5
6
7
8
9
10
3 instanceof Number // false
true instanceof Boolean // false
'abc' instanceof String // false

null instanceof Boolean // false
undefined instanceof Array // false

(3).constructor === Number // true
true.constructor === Boolean // true
'abc'.constructor === String // true

从这里我们也能看出instanceof对于基本类型(string,number,boolean)检测是不太友好的,null和undefined除外,null和undefined能够正确检测完全就是个意外,但是这里如果我们用constructor的方式检测又会很有效.如果想让instanceof对基本类型生效(string,number,boolean),则可以给基本类型加上一层wrapper:

1
2
3
new Number(3) instanceof Number // true
new Boolean(true) instanceof Boolean // true
new String('abc') instanceof String //true

但是这样的检测又是没有任何用的,因为我们事先已经知道了变量的类型(鸡生蛋,蛋生鸡问题)
同时instanceof还存在cross-window的问题:

1
2
3
4
5
6
7
8
9
var iframe = document.createElement('iframe')
document.body.appendChild(iframe)
var iWindow = iframe.contentWindow
iWindow.document.write('<script>var arr = [1, 2, 3]</script>')
iWindow.arr // [1, 2, 3]
iWindow.arr instanceof Array // false

Array === iWindow.Array // false
iWindow.arr instanceof iWindow.Array // true

这里主要凸显出来的问题就是window和iWindow是两个全局对象.如果我们执行iWindow.arr instanceof Array相当于执行iWindow.arr instanceof Window.Array,
同时Window.Array === iWindow.Array是不成立的,所以iWindow.arr instanceof Array返回是false,
当执行iWindow.arr instanceof iWindow.Array就会返回true了.

duck-typing利用鸭子类型做类型检测

简单来说,利用鸭子类型做类型检测就是说,比如你有喉结,那么我就可以认为你是一个男性.也是一样的,如果你有length属性,那么我就认为你是一个Array类型的变量.

1
2
3
4
5
6
7
8
9
function isArray(obj){
return obj!=null && typeof obj === 'object'&& typeof obj.length === 'number'
}
isArray([1,2,3]) //true
isArray({}) //false
isArray(true) //false
isArray(1) //false
isArray(null) //false
isArray(undefined) //false

但是这么做也有明显的缺陷:

1
2
3
var o = {};
o.length = 1;
isArray(o);

但是这里o明显是一个Object,而不是一个Array.鸭子类型的检测方式很容易被绕过去,同时用于测试的属性不一定每个人都是认可的.

Object.prototype.toString做类型检测

Object.prototype.toString检测的原理在于

1
2
3
4
5
6
7
8
Object.prototype.toString.call(3) // "[object Number]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call([1, 2, 3]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"

Object.prototype.toString是Object上的一个方法,通常情况下会被子类覆盖掉(原型链上子类重写toString方法,比如String.prototype.toString).Object.prototype.toString可以可靠的区分各种基本类型和内置的Object类型.可以避免cross-window的问题

1
2
3
4
5
6
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
var iWindow = iframe.contentWindow;
iWindow.document.write('<script>var arr = [1,2,3]</script>')
iWindow.arr;
console.log(Object.prototype.toString.call(iWindow.arr)) // [object Array]

但在区分用户自定义的类型的时候,Object.prototype.toString都是返回[object Object]

1
2
3
4
5
6
function A(){}
function B(){}
var a = new A();
var b = new B();
console.log(Object.prototype.toString.call(a)) // [object Object]
console.log(Object.prototype.toString.call(b)) // [object Object]

总的来说,Object.prototype.toString是一个做类型检测相对可靠的方法.同时对于浏览器端的类型检测并有没有一个完全可靠的方法.类型检测还是要第三方的库.比如浏览器端用underscore去做类型检测,服务端用lodash