2 数据类型检测

2.1 typeof类型判断

在写业务逻辑的时候,经常要用到JS数据类型的判断,面试常见的案例深浅拷贝也要用到数据类型的判断。

typeof

    console.log(typeof 2);               // number
    console.log(typeof true);            // boolean
    console.log(typeof 'str');           // string
    console.log(typeof undefined);       // undefined
    console.log(typeof function(){});    // function
    console.log(typeof Symbol("foo")); // symbol
    console.log(typeof 2172141653n); // bigint
    
    // 不能判别
    console.log(typeof []); // object
    console.log(typeof {}); // object
    console.log(typeof null); // object

优点:能够快速区分基本数据类型 缺点:不能将ObjectArrayNull区分,都返回object

instanceof

    console.log(2 instanceof Number);                    // false
    console.log(true instanceof Boolean);                // false 
    console.log('str' instanceof String);                // false  
    console.log([] instanceof Array);                    // true
    console.log(function(){} instanceof Function);       // true
    console.log({} instanceof Object);                   // true
  • 优点:能够区分ArrayObjectFunction,适合用于判断自定义的类实例对象
  • 缺点:NumberBooleanString基本数据类型不能判断

其内部运行机制是判断在其原型链中能否找到该类型的原型

    class People {}
    class Student extends People {}
    
    const stu = new Student();
    
    console.log(stu instanceof People); // true
    console.log(stu instanceof Student); // true

其实现就是顺着原型链去找,如果能找到对应的 Xxxxx.prototype 即为 true 。比如这里的 stu 作为实例,顺着原型链能找到 Student.prototypePeople.prototype ,所以都为 true

Object.prototype.toString.call()

    var toString = Object.prototype.toString;
     
    console.log(toString.call(2));                      //[object Number]
    console.log(toString.call(true));                   //[object Boolean]
    console.log(toString.call('str'));                  //[object String]
    console.log(toString.call([]));                     //[object Array]
    console.log(toString.call(function(){}));           //[object Function]
    console.log(toString.call({}));                     //[object Object]
    console.log(toString.call(undefined));              //[object Undefined]
    console.log(toString.call(null));                   //[object Null]
  • 优点:精准判断数据类型,所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等
  • 缺点:写法繁琐不容易记,推荐进行封装后使用
    Object.prototype.toString.call(2); // "[object Number]"
    Object.prototype.toString.call(""); // "[object String]"
    Object.prototype.toString.call(true); // "[object Boolean]"
    Object.prototype.toString.call(undefined); // "[object Undefined]"
    Object.prototype.toString.call(null); // "[object Null]"
    Object.prototype.toString.call(Math); // "[object Math]"
    Object.prototype.toString.call({}); // "[object Object]"
    Object.prototype.toString.call([]); // "[object Array]"
    Object.prototype.toString.call(function () {}); // "[object Function]"

在面试中有一个经常被问的问题就是:如何判断变量是否为数组?

    Array.isArray(arr); // true
    arr.__proto__ === Array.prototype; // true
    arr instanceof Array; // true
    Object.prototype.toString.call(arr); // "[object Array]"

判断是否是promise对象

    function isPromise (val) {
        return (
          typeof val.then === 'function' &&
          typeof val.catch === 'function'
        )
    }

2.2 typeof 于 instanceof 区别

typeof 对于基本类型,除了 null都可以显示正确的类型

    typeof 1 // 'number'
    typeof '1' // 'string'
    typeof undefined // 'undefined'
    typeof true // 'boolean'
    typeof Symbol() // 'symbol'
    typeof b // b 没有声明,但是还会显示 undefined

typeof 对于对象,除了函数都会显示 object

    typeof [] // 'object'
    typeof {} // 'object'
    typeof console.log // 'function'

对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug

    typeof null // 'object'

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype

    // 我们也可以试着实现一下 instanceof
    function _instanceof(left, right) {
        // 由于instance要检测的是某对象,需要有一个前置判断条件
        //基本数据类型直接返回false
        if(typeof left !== 'object' || left === null) return false;
    
        // 获得类型的原型
        let prototype = right.prototype
        // 获得对象的原型
        left = left.__proto__
        // 判断对象的类型是否等于类型的原型
        while (true) {
        	if (left === null)
        		return false
        	if (prototype === left)
        		return true
        	left = left.__proto__
        }
    }
    
    console.log('test', _instanceof(null, Array)) // false
    console.log('test', _instanceof([], Array)) // true
    console.log('test', _instanceof('', Array)) // false
    console.log('test', _instanceof({}, Object)) // true

2.3 Object.is和===的区别

Object在严格等于的基础上修复了一些特殊情况下的失误,具体来说就是+0-0NaNNaN。 源码如下

    function is(x, y) {
      if (x === y) {
        //运行到1/x === 1/y的时候x和y都为0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不一样的
        return x !== 0 || y !== 0 || 1 / x === 1 / y;
      } else {
        //NaN===NaN是false,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理
        //两个都是NaN的时候返回true
        return x !== x && y !== y;
      }
    }

2.4 总结

  • typeof
    • 直接在计算机底层基于数据类型的值(二进制)进行检测
    • typeof nullobject 原因是对象存在在计算机中,都是以000开始的二进制存储,所以检测出来的结果是对象
    • typeof 普通对象/数组对象/正则对象/日期对象 都是object
    • typeof NaN === 'number'
  • instanceof
    • 检测当前实例是否属于这个类的
    • 底层机制:只要当前类出现在实例的原型上,结果都是true
    • 不能检测基本数据类型
  • constructor
    • 支持基本类型
    • constructor可以随便改,也不准
  • Object.prototype.toString.call([val])
    • 返回当前实例所属类信息

判断 Target 的类型,单单用 typeof 并无法完全满足,这其实并不是 bug,本质原因是 JS 的万物皆对象的理论。因此要真正完美判断时,我们需要区分对待:

  • 基本类型(null): 使用 String(null)
  • 基本类型(string / number / boolean / undefined) + function: - 直接使用 typeof即可
  • 其余引用类型(Array / Date / RegExp Error): 调用toString后根据[object XXX]进行判断

很稳的判断封装:

    let class2type = {}
    'Array Date RegExp Object Error'.split(' ').forEach(e => class2type[ '[object ' + e + ']' ] = e.toLowerCase()) 
    
    function type(obj) {
      if (obj == null) return String(obj)
      return typeof obj === 'object' ? class2type[ Object.prototype.toString.call(obj) ] || 'object' : typeof obj
    }
Last Updated:
Contributors: leeguooooo