JavaScript温故而知新——类型判断

2,012 阅读4分钟

typeof

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

ES6之前,JavaScript共六种数据类型:Undefined、Null、Boolean、Number、String、Object。使用 typeof 进行类型检测可以看到:

console.log(typeof 1);                  // number
console.log(typeof 'a');                // string
console.log(typeof true);               // boolean
console.log(typeof undefined);          // undefined
console.log(typeof function fn(){});    // function
console.log(typeof {});                 // object
console.log(typeof null);               // object
console.log(typeof []);                 // object
console.log(typeof new Error());        // object

typeof 只能区分"number", "string", "boolean", "undefined", "function", "object"这六种类型。并且对于 Null 和 Object类型,使用 typeof 都会统一返回 “object” 字符串。

要区分 Object 下更多细分的类型,则要用到更加精确的类型判断方法。

Object.prototype.toString

调用 Object.prototype.toString 会返回一个由 "[object " 和 class 和 "]" 组成的字符串,而 class 是要判断的对象的内部属性。来尝试使用这个方法:

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]

function checkType() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)

可以看到不同对象类型调用 Object.prototype.toString 的返回结果都是不同,并且这个结果重点体现在 class 属性的不同。结合 typeof 和 Object.prototype.toString,我们可以封装一个能够检测各种数据类型的方法。

typeof 和 Object.prototype.toString 的结合使用

var class2type = {};

// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    // IE6中,null 和 undefined会被 Object.prototype.toString 识别成 [object Object]
    if (obj == null) {
        return obj + "";
    }
    // 如果是基本类型,就使用 typeof,引用类型就使用 toString。
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}

"纯对象"的判断

所谓"纯对象",指通过 "{}" 或 "new Object"创建的对象(没有原型的对象也是一个"纯对象")。由于 "纯对象"与其他对象如null,数组等,使用 typeof 检测时都会返回 object,因此为了把 "纯对象"区分出来,我们可以学习一下jQuery的 isPlainObject 方法

// 前面写 type 函数时,用来存放 toString 映射结果的对象
var class2type = {};

// 相当于 Object.prototype.toString
var toString = class2type.toString;

// 相当于 Object.prototype.hasOwnProperty
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;

    // 排除掉明显不是obj的以及一些宿主对象如Window
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }

    /**
     * getPrototypeOf es5 方法,获取 obj 的原型
     * 以 new Object 创建的对象为例的话
     * obj.__proto__ === Object.prototype
     */
    proto = Object.getPrototypeOf(obj);

    // 没有原型的对象是纯粹的,Object.create(null) 就在这里返回 true
    if (!proto) {
        return true;
    }

    /**
     * 以下判断通过 new Object 方式创建的对象
     * 判断 proto 是否有 constructor 属性,如果有就让 Ctor 的值为 proto.constructor
     * 如果是 Object 函数创建的对象,Ctor 在这里就等于 Object 构造函数
     */
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

    // 在这里判断 Ctor 构造函数是不是 Object 构造函数,用于区分自定义构造函数和 Object 构造函数
    ## console.log(hasOwn.toString.call(Ctor)); // function Object() { [native code] }
    ## console.log(Object.prototype.toString.call(Ctor)); // [object Function]
    // hasOwn.toString 调用的其实是 Function.prototype.toString
    // Function 对象覆盖了从 Object 继承来的 Object.prototype.toString 方法,函数的 toString 方法返回表示函数源代码的字符串
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}

看一下这个方法的使用结果:

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.create({})))   // false
console.log(isPlainObject(new Person({})))      // false

空对象的判断

即判断一个对象是否有属性

function isEmptyObject( obj ) {
        var name;
        // for循环执行,则说明对象有属性
        for ( name in obj ) {
            return false;
        }
        return true;
}

Window对象

function isWindow( obj ) {
    // Window对象有一个window属性会指向自身
    return obj != null && obj === obj.window;
}

数组和类数组对象判断

同样是jQuery的实现方法

function isArrayLike(obj) {

    // obj 必须有 length属性
    var length = !!obj && "length" in obj && obj.length;
    // 引用前面的 type 函数检测类型
    var typeRes = type(obj);

    // 排除掉函数和 Window 对象
    if (typeRes === "function" || isWindow(obj)) {
        return false;
    }
    // length === 0 是考虑到 arguments 也是一个类数组对象
    // 比如写一个不传参数的函数时,arguments的length为0,但arguments确确实实是一个类数组对象
    return typeRes === "array" || length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
}

(length - 1) in obj表示:对象的最后一个元素必须存在。 举个例子:

var arrLike = {
    2: 3,
    length: 3
}

类数组对象有一个特点是 length 的长度值等于最后一个元素的 key 值加1,假如没有2: 3这一项的话,那么类数组对象也就不成立了。

DOM元素的判断

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

结尾

系列文章: