javaScript之类型判断

javaScript的数据类型

在es6之前javaScript有六种数据类型:

  1. undefined
  2. null
  3. String
  4. Boolean
  5. Object
  6. Number
    通过typeof我们来判断下这些数据类型都对应的是什么。
    1
    2
    3
    4
    5
    6
    console.log(typeof 1)  // number
    console.log(typeof "2") // string
    console.log(typeof {value: 3}) // object
    console.log(typeof undefined) // undefined
    console.log(typeof null) // object
    console.log(typeof false) // boolean

可以看到并不是一一对应的上的
null 是一个object,这是一个历史遗留问题。

1
2
function a() {}
console.log(typeof a) // function

除了null是object,typeof还是能检测出来这些数据类型的,但是在Object中还有很多的细分的类型,如:
Array、Function、Date、RegExp、Error等。
这些类型在typeof的时候,都是object,所以我们需要一个更加强大的类型判断函数。

Object.prototype.toString

当toStirng方法被调用的时候,下面的步骤会被执行,ES5规范是这么说的: ES5 地址

  1. If the this value is undefined, return “[object Undefined]”.
  2. If the this value is null, return “[object Null]”.
  3. Let O be the result of calling ToObject passing the this value as the argument.
  4. Let class be the value of the [[Class]] internal property of O.
  5. Return the String value that is the result of concatenating the three Strings “[object “, class, and “]”.

翻译过来就是:

  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是要判断的对象的内部属性。

1
2
3
4
console.log(Object.prototype.toString.call(undefined)) //[object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]
var date = new Date();
console.log(Object.prototype.toString.call(date)) // [object Date]

class 位置对应的就是我们要判断的数据类型了
现在我们来用Object.prototype.toString()来看看都对应到了那些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(Object.prototype.toString.call(1)) //[object Number]
console.log(Object.prototype.toString.call("2")) //[object String]
console.log(Object.prototype.toString.call({
value: 3
})) // [object Object]
console.log(Object.prototype.toString.call([4, 5])) // [object Array]
console.log(Object.prototype.toString.call(function() {})) // [object Function]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(new Date()))// [object Date]
var reg = /a/g
console.log(Object.prototype.toString.call(reg)) // [object RegExp]
console.log(Object.prototype.toString.call(false)) // [object Boolean]
console.log(Object.prototype.toString.call(new Error())) // [object Error]

除了以上的之外,你还应该知道的

1
2
console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]

以及

1
2
3
4
function a() {
console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
a();

综合上述,Object.prototype.toString能帮我们识别14种类型,[[class]]属性至少有12个。


type API

我们如何写一个自己的类型判断函数呢?

第一版的代码
1
2
3
4
5
6
7
8
var class2type = {};
"Boolean Number String Function Array Date RegExp Object Error Null Undefined".split(" ").map((item, index) => class2type[`object ${item}`] = item.toLowerCase());

function type(obj) {
return typeof obj === "object" || typeof obj === "function" ?
class2type[Object.prototype.toString.call(obj)] || "object" :
typeof obj;
}

看似没有什么问题,但是在ie6的光环加持下,null和undefined都会被Object.prototype.toString识别成[Object Object]

第二版代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var class2type = {};

// 生成class2type映射-- 这里删去了 Null 和 Undefined
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
// 在 == 的判断条件下 undefined == null 是 true
if (obj == null) {
// 隐式转换成了字符串
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[Object.prototype.toString.call(obj)] || "object" :
typeof obj;
}
isFunction

基于上面的type函数我们在来封装一个isFunction的函数

1
2
3
isFunction(obj){
return type(obj) === "function";
}
数组

jQuery判断数组类型,旧版本式通过判断Array.isArray方法是否存在,如果存在就使用该方法,不存在就使用type函数。

1
2
3
var isArray = Array.isArray || function(obj) {
return type(obj) === 'array';
}

到此基本的类型判断就算结束了,但是还有更加复杂的判断,比如plainObject、空对象、Window对象、类数组对象等。


plainObject

plainObject来自于jQuery,译为“纯粹的对象”,就是该对象是通过”{}”或”new OBject”创建的,该对象含有零个或多个键值对。

之所以要判断这个plainObject,是为了区别于Javascript的对象如:null,数组,宿主对象(document)等做区分,因为这些用typeof都会返回object。

jQery中有isplainObject方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name) {
this.name = name;
}

console.log($.isPlainObject({})) // true

console.log($.isPlainObject(new Object)) // true

console.log($.isPlainObject(Object.create(null))); // true

console.log($.isPlainObject(Object.assign({a: 1}, {b: 2}))); // true

console.log($.isPlainObject(new Person('yayu'))); // false

console.log($.isPlainObject(Object.create({}))); // false

除了 {} 和 new Object创建的之外,jQuery认为一个没有原型的对象也是一个纯粹的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false

let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
// 顺着obj的原型一直往上找,最后proto都会等于
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
proto = Object.getPrototypeOf(proto)
}

return Object.getPrototypeOf(obj) === proto
}

console.log(isPlainObject({value: [1,3]})) // true
EmptyObject

jQuery提供了isEmptyObject方法来判断是否是空对象

1
2
3
4
5
6
7
8
9
function isEmptyObject(obj) {
var name;

for(name in obj) {
return false;
}

return true;
}

这里是通过for循环来判断,只要for循环执行了,就说明有属性,有属性就会返回false。
这里是一些判断的例子

1
2
3
4
5
6
7
console.log(isEmptyObject({})); // true
console.log(isEmptyObject([])); // true
console.log(isEmptyObject(null)); // true
console.log(isEmptyObject(undefined)); // true
console.log(isEmptyObject(1)); // true
console.log(isEmptyObject('')); // true
console.log(isEmptyObject(true)); // true
isArrayLike

这个函数的作用就是判断数组和类数组。

1
2
3
4
5
6
7
8
9
10
11
12
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,至少要满足3个条件之一:

  1. 是数组
  2. 长度为0
  3. lengths属性是大于0的数字类型,并且obj[length - 1]必须存在
isElement

判断是不是DOM元素

1
2
3
isElement = function(obj) {
return !!(obj && obj.nodeType === 1);
};