js的实用数据类型判断方法

402 阅读3分钟

typeof

一般用于判断基本数据类型,用于判断引用数据类型和null时会发生意外的错误

优点: 用于判断基本数据很合适(除了null)

缺点:判断引用数据类型时会不准确,如将array类型判断成object类型

原理:不同的对象在底层都表示为二进制,在js里二进制前三位都为0的会 被判断为object类型,null的二进制表示全0(对应机器码的null指针,一般为全0),所以会被判断成object类型。

typeof 1 // number
typeof '1' // string
typeof true // boolean
typeof Symbol('1') // symbol
typeof undefined // undefined

typeof function(){} // function
typeof { a: 1 } // object
typeof [1, 2, 3] // object  这里会判断异常,建议使用Array.isArray区分数组和对象

//以下也会判断异常
typeof new Boolean(true) === 'object';
typeof new Number(1) === 'object';
typeof new String('abc') === 'object';

//最后来看null
typeof null // object

instanceof

它的主要作用是用来判断一个实例是否属于某种类型,用于判断对象很合适

优点:用来判断继承关系和引用数据类型很合适

缺点:由于基本数据类型没有__proto__属性,故不适用于判断基础数据类型

原理:使用__proto__和prototype的属性对比

语法:object instanceof constructor 
object 某个实例对象 constructor 某个构造函数

'abc' instanceof String //false 检查原型链会返回undefined
new String('abc') instanceof String //true
new Boolean(true) instanceof Boolean // true 
new Number(1) instanceof Number // true

顺便做一下简单实现
function new_instance_of(leftVaule, rightVaule) { 
    let leftProto = leftVaule.__proto__; // 取左表达式的__proto__值
    let rightPrototype = rightVaule.prototype; // 取右表达式的 prototype 值
   
    while (true) {
    	if (leftProto === null) {
            return false;	
        }
        if (rightPrototype === rightProto) {
            return true;	
        } 
        leftProto = leftProto.__proto__ 
    }
}

constructor

根据数据类型的构造函数返回类型

优点:对于继承关系和数据类型的判断都比较标准

缺点:null和undefined没有构造函数,会造成无法判断

原理:使用constructor属性来判断

''.constructor == String    //true  
new Number(1).constructor == Number   //true  
new Function().constructor == Function  //true 
true.constructor == Boolean   //true
new Date().constructor == Date  //true

Object.prototype.toString.call()

可以通过 toString() 来获取每个对象的类型。为了每个对象都能通过Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数。

优点:浏览器的内置数据类型都可以准确的判断

缺点:无法判断继承关系,任何实例都会会被返回object类型

var toString = Object.prototype.toString;

toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]

toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]

lodash.baseGetTag和lodash.getTag

lodash.baseGetTag用于判断浏览器的内置数据类型

baseGetTag使用Object.prototype.toString.call和Symbol.toStringTag来判断属性的类型

//lodash.baseGetTag部分重要源码

	//如果值是undefined和null返回对应tag,这里单独排除两个特定值
     if (value == null) {
        return value === undefined ? 
  				'[object Undefined]' 
  				: 
  				'[object Null]'
      }
     // 如果不支持Symbol或者value值上面没有Symbol.toStringTag属性,
     // 直接返回Object.prototype.toString调用后的值    
      if (!(symToStringTag && symToStringTag in Object(value))) {
        return toString.call(value)
      }

lodash.getTag用于判断各种数据类型,包括继承关系,getTag借用baseGetTag和constructor来判断属性的类型,主要用来做以下几种类型判断的兼容处理(DateView、Map、Promise、Set、WeakMap等)

//lodash.getTag部分重要源码

//如果在某些环境下无法判断这5种类型,就做一下兼容处理
if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
        (getTag(new Map) != mapTag) ||
        (getTag(Promise.resolve()) != promiseTag) ||
        (getTag(new Set) != setTag) ||
        (getTag(new WeakMap) != weakMapTag)) {
      baseGetTag = (value) => {
            const result = baseGetTag(value)
            const Ctor = result == '[object Object]' ? value.constructor
            							   : undefined;						
            const ctorString = Ctor ? `${Ctor}` : ''
            //获取值的constructor构造函数属性,如果获取到了就返回对应的tag,
            //如果获取不到就返回原始toString值
            if (ctorString) {
              switch (ctorString) {
                case dataViewCtorString: return dataViewTag
                case mapCtorString: return mapTag
                case promiseCtorString: return promiseTag
                case setCtorString: return setTag
                case weakMapCtorString: return weakMapTag
              }
            }
            return result
  }
}