了解数据类型检测机制以及自己封装instanceOf

279 阅读3分钟

数据类型检测的方法

  • typeOf
  • instanceOf
  • constructor
  • Object.Prototype.toString.call([value])

typeOf

typeOf(value) => 返回结果首先是个字符串,包含对应的数据类型

  • 局限性:

    • typeof null -> "object" ,不能正确的检测null,

    • typeof 检测对象类型值,除了可执行对象{函数}可以检测出来是“function”,其余都是“object”

    • 基于typeof检测一个未被声明的变量,结果是“undefined”,而不会报错「基于let在后面声明则会报错」

    • typeof 检测原始值对应的对象类型的值,结果是“object”

    typeOf检测数据类型的原理: typeof检测数据类型是,按照计算机底层存储的二进制值,来进行检测的,它认为以“000”开始的都是对象,而null全是零,

  • 也不能说typeOf不好,typeOf使用起来简单,而且性能好

instanceOf

[value] instanceof [Ctor] => true/false

  • instanceOf临时用来检测数据类型,本意是检测当前实例是否属于这个类
  • 优点:
    • 好处:细分对象数据类型值「但是不能因为结果是TRUE,就说他是标准普通对象」
  • 弊端:
    • 不能检测原始值类型的值「但是原始值对应的对象格式实例则可以检测」;但是因为原型链指向是可以肆意改动的,所以最后检测的结果不一定准确;

原理:按照原型链检测的;只要当前检测的构造函数(它的原型对象),出现在实例的原型链上,则检测结果就是TRUE;如果找到Object.prototype都没有找到,则结果是FALSE;

几个小例子来加深一下影响吧

// 我们这个方法支持原始值类型
const instance_of = function instance_of(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([], () => {})); //报错 */

constroctor 可以弥补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 */

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

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

  • 大部分内置类的原型上都有toString方法,一般都是转换为字符串的,但是Object.prototype.toString是检测数据类型的,返回值中包含自己所属的构造函数信息...
  • 为什么要用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]”

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]”