一文搞懂javascript的类型判断

191 阅读3分钟

在js中判断数据类型通常有四种方式:

  • typeof
  • Object.prototype.toString
  • instanceof
  • constructor

下面逐步分析它们之间的异同

typeof

console.log(typeof 1) // number
console.log(typeof '1') // string
console.log(typeof true) // boolean
console.log(typeof Symbol()) // Symbol
console.log(typeof undefined) // undefined
console.log(typeof null) // object
console.log(typeof []) // object
console.log(typeof {}) // object
console.log(typeof new RegExp('/A/')) // object
console.log(typeof function(){}) // function

对于基础类型,typeof可以判断出除null以外的所有基础类型 对于引用类型,函数以外的都会被判断成object 因此,typeof并不能准确的判断出一个数据的类型

对于引用类型,我们来看另一种方式:

Object.prototype.toString

console.log(Object.prototype.toString.call(1)) // [object Number]
console.log(Object.prototype.toString.call('1')) // [object String]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(Symbol())) // [object Symbol]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call({})) // [object Object]
console.log(Object.prototype.toString.call(new RegExp('/A/'))) // [object RegExp]
console.log(Object.prototype.toString.call(function(){})) // [object Function]

这样看的话,Object.prototype.toString似乎能准确判断出所有的数据类型 但是需要考虑到这样一种情况:

class A {}
let a = new A()
console.log(Object.prototype.toString.call(a)) // [object Object]

这种方式无法判断出自定义的类型

要准确判断引用类型,我们再看第三种方式:

instanceof

class A {}
let a = new A()
console.log(a instanceof A) // true

console.log([] instanceof Array) // true
console.log([] instanceof Object) // true
// 等价于
console.log([].__proto__ === Array.prototype)
console.log([].__proto__.__proto__ === Object.prototype)

从上面的例子可以看出:instanceof 就是通过判断右侧的值的原型是否在左侧的原型链上

下面,实现自己的instanceof方法

function myInstanceof(A, B) {
    A = A.__proto__
    B = B.prototype 
    while(true) {
    	if (A == null) {
            return false
        }
        if (A === B) {
            return true
        }
        A = A.__proto__
    }
}

class A {}
let a = new A()
console.log(myInstanceof(a, A)) // true
console.log(myInstanceof([], Array)) // true

不建议直接使用__proto__来访问原型,可采用js原生提供的Object.getPrototypeOf()方法改造

遗憾的是,instanceof无法判断基础类型,所以js原生提供的判断方式都无法精确的判断类型

但是官方提供了一个API,可以改造我们的instanceof

Symbol.hasInstance用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。

换句话说就是可以自己定义instanceof的行为,前面我们知道了typeof可以准确判断基础类型 下面,来实现可以判断基础类型的instanceof

class ValidataStr {
	static [Symbol.hasInstance](x) {
    	return typeof x === 'string'
    }
}
console.log('1' instanceof ValidataStr) // true

constructor

"abc".constructor == String // true
(123).constructor == Number // true
(true).construcotr == Boolean // true
[].constructor == Array // true
({}).constructor == Object // true
(function() {}).constructor = Function // true

下面来实现一个通用的类型判断函数,可以判别出基础类型和引用类型

function getType(obj) {
    const type = typeof obj
    // 如果是基础类型,直接返回
    if (type !== 'object') {
        return type
    }
    return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1')
}

最后来看一个对类型判断综合应用的手写题:手写JSON.stringify方法

function jsonStringify(data) {
    let type = typeof data;
    if (type !== 'object') {
        let result = data;
        //data 可能是基础数据类型的情况在这里处理 
        if (Number.isNaN(data) || data === Infinity) {
            //NaN 和 Infinity 序列化返回 "null" 
            result = "null";
        } else if (type === 'function' || type === 'undefined' || type === 'symbol') {
            // 由于 function 序列化返回 undefined,因此和 undefined、symbol 一起处理 
            return undefined;
        } else if (type === 'string') {
            result = '"' + data + '"';
        }
        return String(result);
    } else if (type === 'object') {
        if (data === null) {
            return "null"
            // 第01讲有讲过 typeof null 为'object'的特殊情况 
        } else if (data.toJSON && typeof data.toJSON === 'function') {
            return jsonStringify(data.toJSON());
        } else if (data instanceof Array) {
            let result = [];
            //如果是数组,那么数组里面的每一项类型又有可能是多样的 
            data.forEach((item, index) => {
                if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {
                    result[index] = "null";
                } else {
                    result[index] = jsonStringify(item);
                }
            });
            result = "[" + result + "]";
            return result.replace(/'/g, '"');
        } else {
            // 处理普通对象 
            let result = [];
            Object.keys(data).forEach((item, index) => {
                if (typeof item !== 'symbol') {
                    //key 如果是 symbol 对象,忽略 
                    if (data[item] !== undefined && typeof data[item] !== 'function' && typeof data[item] !== 'symbol') {
                        //键值如果是 undefined、function、symbol 为属性值,忽略 
                        result.push('"' + item + '"' + ":" + jsonStringify(data[item]));
                    }
                }
            });
            return ("{" + result + "}").replace(/'/g, '"');
        }
    }
}

总结:

  • js原生判断数据类型的方式有四种:typeof、 instanceof、 Object.prototype.toString、constructor
  • typeof 可以精确判断出基础类型,但是无法准确判断出具体的引用类型,null会被判断为object
  • Object.prototype.toString 可以精确判断出基础类型和引用类型,但是无法判断自定义的类型
  • instanceof 是通过判断右侧的值的原型是否在左侧的原型链上实现的,可以判断出自定义类型,但是无法判断基础类型,可以通过Symbol.hasInstance来自定义instanceof的行为从而实现对基础类型的判断
  • constructor通过找到构造函数来判断类型