深入JS 之 深度分析JS中检测数据类型的四种方法,看完后再也不怕面试官的提问。

160 阅读4分钟

24. 深度分析JS中检测数据类型的四种方法

No.1 typeof

  • typeof[value] => 检测结果是字符串,包含对应的数据类型
    • 局限性:
    1. typeof null -> object
      • 为啥?typeof检测数据类型是,按照计算机底层存储的二进制值,来进行检测的,它认为以000开始的都是对象,而null全是0
    2. typeof检测对象类型值,除了可执行对象(function/class)可以检测出来是function,其余都是object
    3. typeof检测原始值对应的对象类型的值,结果是object
    • 优点 简单、性能好

No.2 instanceof

  • [value] instanceof [Ctor] => true/false本意是检测当前实例是否属于这个类

    • 局限性:不能检测原始值类型的值[但是原始值对应对的对象格式的实例则可以检测],但是因为原型链指向是可以肆意改动的,所以最后检测的结果不一定准确
    • 优点:相比于typeof可以细分对象数据类型值[但不能因为结果是true,就说他是标准普通对象]
    • 原理 对于上面的介绍,大家可能有些疑问,下面将介绍instanceof的原理,并且我们将自己实现一个instanceof,看完原理解析后再返回来看,相信会有收获的。
    //用ES5的方式创建一个构造函数
    function Fn(){
    
    }
    let f = new Fn
    console.log(f instanceof Fn) // true
    

    对于上面的结果相信大家没有疑问,那他的原理是什么呢,看下面这张图 Symbol.hasInstance.png

    在我们使用f instanceof Fn的时候浏览器会去调用我们要检测的所属构造函数(Fn)的[Symbol.hasInstance]属性,这个属性的值是一个函数,函数的返回值决定了我们的检测结果,ES5的构造函数写法,他的Symbol.hansInstance属性是无法被直接修改,但是在ES6语法中可以被修改。

    注:[Symbol.hasInstance]这个属性在Fn.__proto__上即Function.prototype

    class Fn {
        static[Symbol.hasInstance](obj) {
            console.log('Symbol.hasInstance  执行');
            if (Array.isArray(obj)) return true;
            return false;
        }
    }
    let f = new Fn;
    let arr = [1, 2, 3];
    console.log(f instanceof Fn); //=>false
    console.log(arr instanceof Fn); //=>true
    

    在浏览器控制台执行这段代码,我们可以看出再我们使用instanceof检测的时候浏览器去调用了Fn[Symbol.hasInstance]方法,而且输出的结果并不是我们预期的结果,因为可以被改动,所以检测的结果并不一定是准确的。 Es6-Symbol.hasInstance.png

    • 实现自己的instanceof
    function Fn() {}
    Fn.prototype = Array.prototype;
    let f = new Fn;
    console.log(f instanceof Array);//=>true
    //印证了上面所说的instanceof的局限性
    
    let arr = [10, 20, 30]; //arr.__proto__ ->  Array.prototype -> Object.prototype
    console.log(arr instanceof Array); //=>true
    console.log(arr instanceof RegExp); //=>false
    console.log(arr instanceof Object); //=>true
    console.log(new Number(1) instanceof Number); //=>true
    console.log(1 instanceof Number); //=>false
    /*
    从上面的输出我们大概推测出了[Symbol.hasInstance]方法检测的原理:按照原型链检测的;只要当前检测的构造函数的原型对象,出现在实例的原型链上,则检测结果就是TRUE;如果找到Object.prototype都没有找到,则结果是FALSE;
    */
    //我们这个方法支持原始值类型检测
    const my_instanceof = function my_instanceof(obj, Ctor){
      if(Ctor == null) throw new TypeError('Right-hand side of instanceof is not an object')
      let type = typeof Ctor
      if(!/^(object|function)$/i.test(type)) throw new TypeError('Right-hand side of instanceof is not an object')
      if (!/^function$/i.test(type)) throw new TypeError('Right-hand side of instanceof is not callable')
      if (!Ctor.prototype) throw new TypeError('Function has non-object prototype in instanceof check')
      let proto = Object.getPrototypeOf(obj)
      while(proto){
        if(proto === Ctor.prototype) return true;
        proto = Object.getPrototypeOf(proto)
      }
      return false
    }
    console.log(instance_of([], Array)); //=>true
    console.log(instance_of([], RegExp)); //=>false
    console.log(instance_of(1, Number)); //=>true
    console.log(instance_of(Symbol(), Symbol)); //=>true
    console.log(instance_of([], () => {})); //报错 Function has non-object prototype in instanceof check
    

No.3 constructor

constructor的修改比instanceof更'肆无忌惮'

let arr = [],
    n = 10,
    m = new Number(10);
console.log(arr.constructor === Array); //=>true
console.log(arr.constructor === RegExp); //=>false
console.log(arr.constructor === Object); //=>false 
// 如果CTOR结果和Object相等,说明当前可能是标准普通对象
console.log(n.constructor === Number); //=>true
console.log(m.constructor === Number); //=>true

function Fn() {}
let f = new Fn;
console.log(f.constructor === Fn); //=>true
console.log(f.constructor === Object); //=>false

function Fn() {}
Fn.prototype = {}; //重定向后丢失了constructor
let f = new Fn;
console.log(f.constructor === Fn); //=>false
console.log(f.constructor === Object); //=>true 

No.4 Object.prototype.toString.call([value])

  • 这是JS中唯一一个检测数据类型没有任何瑕疵的「最准确的,除了性能比typeof略微少那么一丢丢,写起来略微麻烦那么一丢丢」

Object.prototype.toString.call([value]) => "[object Number/String/Boolen/Null/Undefined/Symbol/BigInt/Object/Function/Array/RegExp/Date/Math/Error...]"大部分内置类的原型上都有toString方法,一般都是转换为字符串的,但是Object.prototype.toString是检测数据类型的,返回值中包含自己所属的构造函数信息...

  • 1.为啥要用call?
    • ([]).toString() 调用的是Array.prototype上的toString,是转换为字符串
    • ({}).toString() 调用的是Object.prototype上的toString,是检测数据类型的「方法中的this是谁,就是检测谁的类型」 -> 我们需要把Object.prototype.toString执行,而且还要改变其中的this -> ({}).toString.call(10) =>“[object Number]”
  • 2.检测返回值遵循啥规则?
    • 一般都是返回当前实例所属的构造函数信息,但是如果实例对象拥有 Symbol.toStringTag 属性,属性值是啥,最后返回的就是啥,例如:Math[Symbol.toStringTag]="Math" => Object.prototype.toString.call(Math) “[object Math]” Object.prototype.toString.call().png
    class Fn {
      // constructor() {
      //     this[Symbol.toStringTag] = 'Fn';
      // }
      [Symbol.toStringTag] = 'Fn';
    }
    let f = new Fn;
    // console.log(Object.prototype.toString.call(Fn)); //[object Function]
    console.log(Object.prototype.toString.call(f)); //[object Fn]
    
  • 3.自己封装一个检测数据类型的方法
const checkType = function checkType(obj) {
    if (obj == null) return obj + ""; //判断null和undefined
    const reg = /^\[object ([a-zA-Z0-9]+)\]$/i;
    return typeof obj === "object" || typeof obj === "function" ?
        reg.exec(Object.prototype.toString.call(obj))[1].toLowerCase() :
        typeof obj;
};

如果觉得还可以,麻烦XDM点个赞,点击下面的进入专栏可以看到其他的文章!!!!