基础回顾:js的类型及检测方法

120 阅读6分钟

快速导航

类型介绍

任何一种计算机编程语言,数据类型都是基础,在js中共有两大类类型:基本类型引用类型(ps有的也叫对象类型);

基本类型共六种:undefined、null、Symbol、布尔值(Boolean)、字符串(String)、数值(Number);

备注:

  1. string、boolean、number、null、undefined称为原始类型,表示js最基础最原始的规定类型。

  2. symbol为ES6中新增的数据类型,表示独一无二的值。使用symbol类型不能使用new 进行初始化,直接Symbol()即可。

  3. null和undefined 是js里的特殊值,null表示空。undefined表示未定义。

引用类型:一般来说除了基础类型的其他类型都属于引用类型,如Function(定义类)、Array、Date、Object等。

引用类型和基本类型的区别:

  1. 存储位置:基本类型的值直接存储在栈内存中,引用类型的真实存储在对内存的地址保存在栈内存中,其值保存在堆内存中。

  2. 克隆情况:基本类型直接通过赋值的方式给另一变量即可克隆一个变量,但是引用类型直接赋值克隆,只是将栈内存中的值引用地址赋值给另一变量,结果两个变量指向的是同一堆内存中的地址。所以操作其中一个变量,另一个变量也会被修改。(ps:将一个变量的赋值给另一变量,只会将该变量栈内存中的复制值保存给另一变量,不会操作堆内存)

类型判断

为什么要进行类型判断?

个人觉得因为js是弱类型语言,其声明的变量可以被替换成任何类型,并且不会进行类型校验,这虽然使得使用非常灵活,但是这会导致一些操作报错。 例如: 声明一个函数将字符串转为大写,但是调用该函数的时候传入的传入是数值.由此会导致程序报错退出运行。

 let test= function(name){
     name.toUpperCase()
 }

 test(11)

故此,我们要给他添加类型判断使得程序健壮性更好

 let test= function(name){
     if(typeof name ==='string'){
         name.toUpperCase()
     }
 }

 test(11)

一般来说,js判断变量类型的方法有:typeof、instanceof、constructor、Object.prototype.toString.call()

typeof

typeof 只能分辨一个变量是string、boolean、number、undefined、symbol;

   console.log(typeof 'a');    //->string
   console.log(typeof 1);  //->number
   console.log(typeof undefined);  //->undefined
   console.log(typeof Symbol());   //->symbol
   console.log(typeof false);  //->boolean
   console.log(typeof function(){});   //->function

   console.log(typeof null);   //->object
   console.log(typeof new function(){});   //->object
   console.log(typeof {}); //->object
   console.log(typeof []); //->object

由上可以看出typeof无法分辨出null及对象的准确类型。为了解决这一问题js提供了另一个检测类型的操作符 instanceof

instanceof

与typeof相对应,instanceof只能检测对象的类型;

     console.log( 'a' instanceof String)    //false
     console.log( new String('a')  instanceof String)   //true
     console.log( 1 instanceof Number)  //false
     console.log( function(){} instanceof Function) //true
     console.log( new function(){} instanceof Function) //false
     console.log( new function(){} instanceof Object)   //true
    
     console.log( [] instanceof Array)  //true
     console.log( {} instanceof Object) //true
     console.log( {} instanceof Map)    //false
     console.log( new Map() instanceof Map) //true
     console.log( new Map() instanceof Object)  //true 注意这里为true的原因是所有对象都会继承全局Object对象
     console.log( new Set() instanceof Set) //true

      console.log( null instanceof null)    //报错
      console.log( undefined instanceof undefined)  //报错

由上可以看出instanceof的使用方式为 变量 instanceof 类型。instanceof检测的是原型,内部机制是通过判断原型链中是否有类型的原型。也就是说只要B是A的子类,(B instanceof A )(A instanceof A) 都将返回true。这导致无准确的判断该变量是A的实例还是B的。同时我们看到 instanceof 的右侧必须是object,否则就报错,故其检测有限。

constructor

和instanceof相差不大。利用constructor进行类型判断只能判断变量类型是否为引用类型。该原理是利用当函数F被定义时,JS引擎会为prototype添加原型,然后在prototype上添加constructor属性,该属性会指向F,当用F创建对象的时候,prototype上的constrctor会复制到新对象上。即可用构造函数的类型确定当前变量的类型是否与他相同。


     'a'.constructor== String;  //true
     [].constructor== Array ;   //true
     {}.constructor== Object;   //true
     funtion(){}.constructor== Function;    //true
     new Number(1).constructor==Number; //true    
     new Map().constructor==Map;    //true    
     new Set().constructor==Set;    //true    
     new Number(1).constructor==Number; //true    
     Symbol().constructor==Symbol;  //true    
     1..constructor ==Number;    //true(ps:这里调用多加一点的原因是只有一个点会被当成小数点而报错)

由上可知被检测的变量,必须具有constructor属性,当遇到没有该属性的变量值。如:undefined,null。会导致报错。除此之外当B通过原型继承A类时,new B().constructor ==B的值为false;new B().constructor ==A 的值为true。若想要检测正确,则需要手动修改constructor的指向。

Object.prototype.toString.call()

对比与typeof、instanceof、constructor等检测方式,Object.prototype.tostring.call()能够检测所有非自定义的数据类型。

其内部检测实现分为以下几步:

  1. 先判断this值,如果this值为undefined 则返回 "[object Undefined]",如果this值为null,则返回"[object Null]"

  2. 调用toObject(this)将this转化为一个对象O。注意:下面用于判断的属性或方法都是在这里,根据传入对象的类型生成的。

  3. 判断对象O是否是数组,若是则声明一个builtinTag并赋值"Array";

    若不是则判断O是否有[[ParameterMap]]属性,若有则声明一个builtinTag并赋值"Arguments";

    若没有则判断O是否有[[Call]]方法,若有则声明一个builtinTag并赋值"Function";

    若没有则判断O是否有[[ErrorData]]属性,若有则声明一个builtinTag并赋值"Error";

    若没有则判断O是否有[[BooleanData]]属性,若有则声明一个builtinTag并赋值"Boolean";

    若没有则判断O是否有[[NumberData]]属性,若有则声明一个builtinTag并赋值"Number";

    若没有则判断O是否有[[StringData]]属性,若有则声明一个builtinTag并赋值"String";

    若没有则判断O是否有[[DateValue]]属性,若有则声明一个builtinTag并赋值"Date";

    若没有则判断O是否有[[RegExpMatcher]]属性,若有则声明一个builtinTag并赋值"RegExp";

    如果还是没有则给builtinTag赋值"Object"

  4. 调用Get(O,@@toStringTag)方法,并将结果赋值给变量tag

  5. 调用Type(tag),判断其返回结果是否为String,如果不是则将builtinTag赋值给tag

  6. 生成"[object"+ tag +"]"这个字符串并返回

实验验证如下:

    Object.prototype.toString.call(1);  //"[object Number]"
    Object.prototype.toString.call('a');    //"[object String]"
    Object.prototype.toString.call(function(){});   //"[object Function]"
    Object.prototype.toString.call(new function(){});   //"[object Object]"
    Object.prototype.toString.call({});     //"[object Object]"
    Object.prototype.toString.call([]);     //"[object Array]"
    Object.prototype.toString.call(new Map());  //"[object Map]"
    Object.prototype.toString.call(new WeakMap());  //"[object WeakMap]"
    Object.prototype.toString.call(new Set());  //"[object Set]"
    Object.prototype.toString.call( Symbol());  //"[object Symbol]"

由上可知Object.prototype.toString.call();方法并不能检测自定义对象的类型。不过对于原生的类型检测该方法的功能无疑是最好的。

总结

对于类型检测一般来说,原生类型使用Object.prototype.toString.call(),自定义使用instanceof或者constructor,不过constructor有点繁琐。因此建议自己选用时权衡一下难易以及是否埋坑。

最后内容难免出错,欢迎指正,交流。