一半前端开发者都不懂的的数据类型检测原理

212 阅读4分钟

1.typeof [value]:检测数据类型的运算符

1.1返回结果是一个字符串,其次字符串中包含对应的数据类型,例如:“number”、“object”、“function”..

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof []);              // object     []数组的数据类型在 typeof 中被解释为 object
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null);            // object     null 的数据类型被 typeof 解释为 object

1.2 弊端

  1. typeof null 的结果为 “object”

  2. typeof 对象 -> 除函数对象被识别为“function”,其余的对象都是返回“object”

  3. typeof 未被声明的变量 -> 不会抱错,而是“undefined”

    除了这些以外,用typeof检测原始值类型「或者函数类型」还是非常的方便、准确的

1.3 检测原理

所有的数据类型值在计算机底层都是按照二进制来进行存储的,而typeof检测类型,也是按照二进制值来进行检测的「特点:性能好」;所有以 000 开始的,都是对象类型,再排查一下有没有实现call,实现的是函数对象,没实现的就是其余的对象...而null这个值存储的二进制都是零,所以也被识别为“object”!!

2.instanceof:检测当前实例是否属于这个类「临时拉来做数据类型检测」

2.1 用法

实例 instanceof 构造函数,是返回true,否则返回false

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    
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);

2.2 作用

  1. instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型[实现了对typeof 的补充];

2.3 弊端

  1. 无法基于instanceof检测是否为标准普通对象「纯粹对象:直接是Object类的实例」;因为所有对象都是Object类的实例,基于“xxx instanceof Object” 检测的时候,返回结果都是true;
  2. 无法检测原始值类型的值;因为基于instanceof检测的时候,默认不会进行“装箱”操作!!
  3. 检测的结果不一定严谨;因为,原型链以及所属构造函数是谁,是可以用户自己去改变的!!

2.4 instaceof 调用的原理

  1. 首先调用 [Ctor]Symbol.hasInstance 这个函数,如果存在这个函数,则直接基于这个函数处理即可
  2. 当代浏览器基本都有,因为Symbol.hasInstance在Function.prototype中,每一个构造函数都是Function的实例,都可以调用Function.prototype[Symbol.hasInstance]这个方法
  3. 如果不存在这个函数,浏览器会按照当前[value]原型链一层层向上查找,直到找到Object.prototype为止;查看[Ctor].prototype是否出现在它的原型链中,如果出现了,则结果是true,说明[value]是[Ctor]的实例,反之则为false...

2.5 分析instanceof的优缺点和底层实现机制,并且重写instanceof

const instance_of = function instance_of(value, Ctor) {
    // 保证Ctor的格式也是正确的
    if (typeof Ctor !== "function"throw new TypeError('Right-hand side of instanceof is not callable');
    if (!Ctor.prototypethrow new TypeError('Function has non-object prototype in instanceof check');
    // 不支持原始值类型值的检测
    if (value === nullreturn false;
    if (!/^(object|function)$/.test(typeof value)) return false;

    // 支持Symbol.hasInstance方法的直接按照这个处理
    if (typeof Ctor[Symbol.hasInstance] === "function"return Ctor[Symbol.hasInstance](value);

    // 不支持的则按照原型链一层层的查找即可  Object.getPrototypeOf(value):获取value所属构造函数的原型对象
    let proto = Object.getPrototypeOf(value);
    while (proto) {
        // Ctor.prototype出现在了value的原型链上「value是Ctor的实例对象」:直接返回true & 结束查找
        if (proto === Ctor.prototypereturn true;
        proto = Object.getPrototypeOf(proto);
    }
    return false;
};

3 constructor :获取当前实例所属的构造函数「临时可做数据检测」

3.1 用法

@1 [value].constructor 获取其构造函数,验证是否为我们想检测的类

​ 例如:[value].constructor===Array

3.2 相比较于instanceof来讲

  1. 可以检测原始值类型的值「排除null/undefined」
  2. 检测是否为标准普通对象 [value].constructor===Object
  3. 和instanceof一样,检测的结果仅供参考「constructor这个值是可以被肆意修改的」
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true

这里有一个坑,如果我创建一个对象,更改它的原型,constructor就会变得不可靠了

function Fn(){};
 
Fn.prototype=new Array();
 
var f=new Fn();
 
console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true 

4 .**Object.prototype.toString.call()**专门检测数据类型的办法

Object.prototype.toString.call([value]):专门检测数据类型的办法

​ @1 调用Object.prototype.toString方法,让方法中的this指向检测的值,就是检测当前值的数据类型;

​ 返回结果是一个字符串 “[object ?]”

​ 例如:Object.prototype.toString.call(10) -> "[object Number]"

​ Object.prototype.toString.call(new Number(10)) -> "[object Number]"

​ 它是所有检测数据类型的办法中,最强大、最稳定...的方式{除了写起来麻烦一些}

​ @2 返回结果是 “[object ?]”,“?” 会是啥呢?

​ + 首先获取[value][Symbol.toStringTag]属性值,如果存在这个属性,则这个属性值是啥,“?”就是啥!

​ + 如果没有这个属性,一般“?”是当前实例所属的构造函数!! Symbol.prototype & BigInt.prototype & Math & GeneratorFunction.prototype & Promise.prototype & Set.prototype & Map.prototype ... 这些类的原型上,都有Symbol.toStringTag这个属性!!

​ Object.prototype.toString这个方法是用来检测数据类型的,而且方法内部规定:方法中的this是谁,我们就检测谁的类型,所以我们基于call方法去改变this指向!!!

toString()Object 的原型方法,调用该方法,可以统一返回格式为 “[object Xxx]” 的字符串,其中 Xxx 就是对象的类型。对于 Object 对象,直接调用 toString() 就能返回 [object Object];而对于其他对象,则需要通过 call 来调用,才能返回正确的类型信息。我们来看一下代码。

Object.prototype.toString({})       // "[object Object]"
Object.prototype.toString.call({})  // 同上结果,加上call也ok
Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call('1')  // "[object String]"
Object.prototype.toString.call(true)  // "[object Boolean]"
Object.prototype.toString.call(function(){})  // "[object Function]"
Object.prototype.toString.call(null)   //"[object Null]"
Object.prototype.toString.call(undefined//"[object Undefined]"
Object.prototype.toString.call(/123/g)    //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([])       //"[object Array]"
Object.prototype.toString.call(document)  //"[object HTMLDocument]"
Object.prototype.toString.call(window)   //"[object Window]"

实现一个全局通用的数据类型判断方法,来加深你的理解,代码如下

function getType(obj){
  let type  = typeof obj;
  if (type !== "object") {    // 先进行typeof判断,如果是基础数据类型,直接返回
    return type;
  }
  // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
  return Object.prototype.toString.call(obj).replace(/^[object (\S+)]$/, '$1');  // 注意正则中间有个空格
}
/* 代码验证,需要注意大小写,哪些是typeof判断,哪些是toString判断?思考下 */
getType([])     // "Array" typeof []是object,因此toString返回
getType('123')  // "string" typeof 直接返回
getType(window) // "Window" toString返回
getType(null)   // "Null"首字母大写,typeof null是object,需toString来判断
getType(undefined)   // "undefined" typeof 直接返回
getType()            // "undefined" typeof 直接返回
getType(function(){}) // "function" typeof能判断,因此首字母小写
getType(/123/g)      //"RegExp" toString返回

小结

  • 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]进行判断

jQ中封装的数据类型检测,检测一个对象是不是个纯对象

// 检测数据类型的方法封装
(function () {
    var getProto = Object.getPrototypeOf// 获取实列的原型对象。
    var class2type = {};
    var toString = class2type.toString;
    var hasOwn = class2type.hasOwnProperty;
    var fnToString = hasOwn.toString;
    var ObjectFunctionString = fnToString.call(Object);

    [
        "Boolean",
        "Number",
        "String",
        "Symbol",
        "Function",
        "Array",
        "Date",
        "RegExp",
        "Object",
        "Error"
    ].forEach(function (name) {
        class2type["[object " + name + "]"] = name.toLowerCase();
    });

    function toType(obj) {
        if (obj == null) {
            return obj + "";
        }
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[toString.call(obj)] || "object" :
            typeof obj;
    }
    // 判断是不是存对象。
    function isPlainObject(obj) {
        var proto,
            Ctor,
            type = toType(obj);
        if (!obj || type !== "object") { // 如果类型检测不是对象直接返回。-+
            return false;
        }
        proto = getProto(obj); // 获取实列对象的原型对象。
        if (!proto) {
            return true;
        }
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
        return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
    }

    window.toType = toType;
    window.isPlainObject = isPlainObject;
})();