JS数据类型四种检测方式详解及优缺点

501 阅读6分钟

复习数据类型

js数据类型

一、typeof

typeof1.png

定义:检测数据类型的运算符

语法:tyepof [value]

返回值:是一个字符串,字符串中包含了对应的数据类型

  • typeof 检测的结果首先是一个字符串;
  • 字符串中包含了对应的数据类型(如:"number"、"string"、"boolean"、"undefined"、"object"、"function"、"symbol"、"bigint")
  • 多个typeof检测 typeof typeof typeof [12,20,30]=>'string' 由于typeof返回的结果永远是一个字符串(字符串中包含了对应的类型),所以连续出现两个及两个以上typeof检测的时候,最后结果都是 "string"
typeof []===>"object"
typeof typeof []===>"string"

原理:按照二进制值检测

  • 000:对象
  • 1:整数
  • 010:浮点数
  • 100:字符串
  • 110:布尔
  • undefined:-2^30
  • null:全是零

优点:检测性能高

缺点:不能细分对象

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

  • 检测对象数据类型,只有funciton是function 其他的对象类型(检测数组array、正则、普通对象)都是object ,所以用typeof 检测的话,并不能细分。

  • typeof function 为什么能检测出 "function" 因为function检验对象的时候先判断是不是调用call方法

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

  • typeof null -> "object"

问题:typeof null=>"object" typeof {name:'东方淼淼'}为什么也等于 "object"

点我了解浏览器对于各种数据类型的处和有关进制的问题

因为是根据二进制检测 null和对象的前三位相同

typeof检测数据类型是,按照计算机底层存储的二进制值,来进行检测的,它认为以“000”开始的都是对象,而null全是零,所以被识别为对象,但是他不是对象 是基本数据类型值

练习题

typeof "zhufeng" ====> "string"
typeof 12  ===> "number"
typeof false   ===>"boolean"
typeof true ====>  "boolean"
typeof null ====> "object"
typeof undefined ====>"undefined"
typeof Symbol()=>"symbol"
typeof 456n=>"bigint"
//引用数据类型,除了function,typeof检测是function,其余检测返回的都是“object”
typeof [1,2] =====>"object"
typeof function(){} =>"function"

二、instanceof

instanceof.png

定义:是检测当前实例是否属于这个类

语法:[value] instanceof [Ctor]

属于返回true,不属于返回false

2-1.png

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

好处:细分对象数据类型值

  • 想检测这个值是否为数组,只需要看是否为Array类的实例即可
  • 「但是不能因为结果是TRUE,就说他是标准普通对象」

弊端:

  • 不能检测原始值类型的值「但是原始值对应的对象格式实例则可以检测」;
  • 但是因为原型链指向是可以肆意改动的,所以最后检测的结果不一定准确;
let arr = [10, 20, 30];
console.log(arr instanceof Array); //=>true
console.log(arr instanceof RegExp); //=>false
console.log(arr instanceof Object); //=>true  不管是数组对象还是正则对象,都是Object的实例,检测结果都是TRUE,所以无法基于这个结果判断是否为标准普通对象
console.log(new Number(1) instanceof Number); //=>true
console.log(1 instanceof Number); //=>false 可以看出不能检测原始值类型

底层原理处理

f instanceof Fn  -> Fn[Symbol.hasInstance](f)
Function.prototype[Symbol.hasInstance]=function...
所有的函数都是Function这个类的实例
所有的函数都拥有这个属性

函数底层处理机制.png

普通写法的构造函数 它的Symbol.hasInstance属性无法被直接修改的,但是ES6语法中可以!! 通过这种机制,可以通过重写的方式改变默认的结果 ,把它变成自己想要的结果

//普通写法的构造函数==================
//function Fn() { };
//Fn[Symbol.hasInstance]=function(){};
//控制台输出Fn[Symbol.hasInstance]=>ƒ [Symbol.hasInstance]() { [native code] }

//let f = new Fn;
//console.log(f instanceof Fn);//=>true
//console.log(Fn[Symbol.hasInstance](f));//=>true

//ES6写法=================================================================
 class Fn {
            static [Symbol.hasInstance](obj) {
                console.log('OK');
                if (Array.isArray(obj)) return true;
                return false;
            }
        };
let f=new Fn();
let arr = [10, 20, 30];
console.log(f instanceof Fn);//=>false
console.log(arr instanceof Fn); //=>true

自己重写instanceof

instanceof 右侧必须是个对象 还是个函数对象 而且必须得有prototype

装箱.png

//obj 检测的实例 Ctor构造函数
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;
    //=>不是object和function
    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');
    //=>验证有没有prototype 例如箭头函数没有prototype
    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([],{})); //报错 

三、constructor

constructor.png

定义:判断当前的实例的constructor的属性值是不是预估的类

语法:实例.constructor === 类

返回值:属于返回TRUE,不属于返回FALSE

优点

  • 实例.constructor 一般都等于类.prototype.constructor也就是当前类本身 (前提是constructor 并没有被破坏)
  • 能检测基本数据类型

局限性:不能够给当前类的原型进行重定向,会造成检测的结果不准确

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

Fn.prototype={}; //更改重定向后 就没有了constructor
console.log(f.constructor===Fn);//=>false
console.log(f.constructor===Object);//=>true

四、Object.prototype.toString.call()

a.png

优点:这是JS中唯一一个检测数据类型没有任何瑕疵的

语法:

  • Object.prototype.toString([value]);
  • ({}).toString.call(被检测的实例)

返回值:是一个字符串"[object当前被检测实例所属的类]"

  • [object Number/String/Boolen/Null/Undefined/Symbol/BigInt/Object/Function/Array/RegExp/Date/Math/Error...]"

大部分内置类的原型上都有toString方法,一般都是转换为字符串的,

  • 如:Array.prototypeNumber.prototype/Boolean.prototype/Function.prototype ...
  • 除了 Object.prototype.toString是检测数据类型的,返回值中包含自己所属的构造函数信息...
//通过这一点就比typeof强
Object.prototype.toString.call(1);//=>"[object Number]"
Object.prototype.toString.call(new Number);//=>"[object Number]"
Object.prototype.toString.call(1);//"[object Number]"
Object.prototype.toString.call('');//"[object String]"
Object.prototype.toString.call(true);//"[object Boolean]"
Object.prototype.toString.call(null);//"[object Null]"
Object.prototype.toString.call(undefined);//"[object Undefined]"
Object.prototype.toString.call(Symbol());//"[object Symbol]"
Object.prototype.toString.call(12n);//"[object BigInt]"
Object.prototype.toString.call(/\d+/);//"[object RegExp]"
Object.prototype.toString.call([]);//"[object Array]"
Object.prototype.toString.call(()=>{});//"[object Function]"
Object.prototype.toString.call({});//"[object Object]"
Object.prototype.toString.call(Math);//"[object Math]"
Object.prototype.toString.call(function*(){});//"[object GeneratorFunction]"

原理

  • ([]).toString() 调用的是Array.prototype上的toString,是转换为字符串
  • ({}).toString() 调用的是Object.prototype上的toString,是检测数据类型的「方法中的this是谁,就是检测谁的类型」
  • Object.prototype.toString.call([value]) 我们需要把Object.prototype.toString执行,基于call方法 改变其中的this
  • ({}).toString.call(10)=>"[object Object]" 返回值的规则
  • 一般都是返回当前实例所属的构造函数信息
  • 但是如果实例对象拥有 Symbol.toStringTag 属性,属性值是啥,最后返回的就是啥
Math[Symbol.toStringTag]="Math"  => Object.prototype.toString.call(Math)  “[object Math]”
//class Fn {};
//let f=new Fn;
//console.log(Object.prototype.toString.call(f));//[object Object]
//======>怎么样让结果等于[object Fn] 以下例子
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]” 

总结

四种检测数据类型.png