JS中的数据类型检测汇总

93 阅读4分钟

常见类型判断 typeof,instanceof,constructor,Object.prototype.toString.call()有什么不同?让你知道各种边界,玩转类型判断

基本数据类型判断

typeof 操作符

主要用于JS中基本数据类型(值类型)的判断,返回值有string, number, bigint, boolean, undefined, symbol, object, function 八个值

console.log(typeof 42);
// output: "number"

console.log(typeof 'blubber');
// output: "string"

console.log(typeof true);
// output: "boolean"

console.log(typeof undeclaredVariable);
// output: "undefined"

function Car(){}
console.log(typeof new Car());
// output: "object" 无法检测自定义类型

// 注意:⚠️
typeof null === 'object'; // true
null == undefined // true
null === undefined // false

引用数据类型判断

构造函数、原型和实例的关系:每个构造函数都有一个原型对象(prototype属性),原型对象有一个属性(constructor属性)指回构造函数,而该构造函数创建的实例有一个内部指针[[Prototype]]/__proto__指向原型对象prototype ————来自JavaScript高级程序设计

1. Constructor

直接判断构造函数

//1.  基本数据类型
var num = 1;
num.constructor === Number //true
''.constructor === String // true
true.constructor === Boolean   //true
// 2. 原生构造函数
new Date().constructor === Date  // 原声true
new Error().constructor === Error    //true
[].constructor === Array    //true
document.constructor === HTMLDocument    //true
// 自定义
function Car(){} 
(new Car()).constructor === Car; //true
// 3. null | undefined 报错
null.constructor //Uncaught TypeError: Cannot read properties of null (reading 'constructor')
undefined.constructor //Uncaught TypeError: Cannot read properties of undefined (reading 'constructor')

2. instanceof 操作符

instance instanceof constructorFun 检测构造函数原型,也就是constructorFun.prototype是否出现在该实例对象instance的原型链上

[] instanceof Array; //true
{} instanceof Object; //true
[] instanceof Object //true 继承
new Date() instanceof Date;//true
new Date() instanceof Object;//true 继承

(1) 可以检测自定义类型

function Car(){}
console.log(new Car() instanceof Car); //true
console.log((new Car()) instanceof Object) //继承

(2) 不能检测js基本数据类型

var num1 = 123;
num1 instanceof Number; //false :不能检测js基本数据类型
var num2 = new Number(123);
num instanceof Number; //true

// 注意:⚠️
null instanceof Object //false
undefined instanceof Object// false

原始值包装类型123与引用类型new Number(123)的区别

详见(javascript的高级程序设计5.3 原始值包装类型)介绍,摘抄如下:

引用类型与原始值包装类型的主要区别在于对象的生命周期。用下面的例子理解:

let s1 = "some text";
s1.color = "red"; //临时创建一个 String 对象,后销毁
console.log(s1.color);  // undefined 创建了自己的 String 对象,但这个对象没有 color 属性

instanceof的欺骗行为案例

function foo() {} 
const bar = { a: 'a'};

foo.prototype = bar; //串改foo.prototype
console.log(bar instanceof foo); // false:bar的原型链上只有Object.prototype,foo.prototype不在bar的原型链上

const baz = Object.create(bar); // 使用bar作为原型,创建baz对象

console.log(baz instanceof foo); // true:baz的原型是bar,而foo.prototype = bar,所以相等

instanceof的本质理解

下面例子理解object instanceof Constructor,instanceof 运算符的检测本质是用来检测 constructor.prototype 是否存在于参数 object 的原型链上,注意⚠️:所有引用类型都继承自 Object

// 定义构造函数
function C(){}
function D(){}

var o = new C(); // o的原型链C.prototype => Object.prototype

o instanceof C; // true o的原型指向C.prototype
Object.getPrototypeOf(o) === C.prototype

o instanceof D; // false,因为 D.prototype 不在 o 的原型链上

o instanceof Object; // true
Object.prototype.isPrototypeOf(o) //true
C.prototype instanceof Object // true

C.prototype = {};
var o2 = new C(); //o2的原型链 {} => Object.prototype

o2 instanceof C; // true

o instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上。

D.prototype = new C(); // 继承
var o3 = new D(); // o3的原型链 D.prototype =>C实例 => {} => Object.prototype
o3 instanceof D; // true
o3 instanceof C; // true 因为 C.prototype 现在在 o3 的原型链上

3. Object.prototype.toString.call()

  • JS内置的对象类型返回[object Type]
  • 只支持原生的类型,对于自定义的类型永远是[object Object]
Object.prototype.toString.call('') ;       // [object String]
Object.prototype.toString.call(1) ;        // [object Number]
Object.prototype.toString.call(true) ;     // [object Boolean]
Object.prototype.toString.call(Symbol());   //[object Symbol]
Object.prototype.toString.call(undefined) ;  // [object Undefined]
Object.prototype.toString.call(null) ;     // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ;   // [object Date]
Object.prototype.toString.call([]) ;         // [object Array]
Object.prototype.toString.call(new RegExp()) ;   // [object RegExp]
Object.prototype.toString.call(new Error()) ;   // [object Error]
Object.prototype.toString.call(document) ;   // [object HTMLDocument]

//自定义的类型永远是`[object Object]`
function Car(){}
console.log(Object.prototype.toString.call(new Car())); //[object Object] 

总结

  1. 基本数据类型用typeof,要注意typeof null === 'object';为true
  2. 需要判断是否在原型链上用instanceof注意⚠️:undefined | null instanceof Object为false
  3. constructor直接判断构造函数,在自定义类型上,要担心开发者重写 prototype 后,可能原有的 constructor 引用会丢失,注意⚠️:undefined | null.constructor 报错
  4. Object.prototype.toString.call(value)只支持JS的原生对象类型,返回[object Type]

例子

关于React中Redux里面的isPlainObject的写法如下所示:

  • Plain Object:纯粹的对象(通过 "{}" 或者 "new Object" 创建的)
export default function isPlainObject(obj: any): boolean {
  // 用typeof,注意了null的区别
  if (typeof obj !== 'object' || obj === null) return false
  
  // 寻找原型链的顶端
  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

其他常用场景

判断NaN Number.isNaN() VS isNaN()

  • isNaN(value) 会对value进行转换后再判断 Eg: isNaN("123ABC") // true =》 因为 Number("123ABC")NaN(隐式转换)
  • 推荐使用 Number.isNaN("123ABC") // false 不会对值进行隐式转换
Number(); //0
Number(undefined); // NaN
Number(""); // 0
Number(null); // 0
Number(true); // 1
Number(false); //0
Number("12.00"); // 12

数组检测 Array.isArray(value)

推荐使用,解决了instanceof Array不同iframes检测不了的情况,详见

检查对象是否为空

const isEmptyObj = (value)=>value instanceof Object && Reflect.ownKeys(value).length === 0 && value.constructor === Object;
console.log(isEmptyObj({}))//true
console.log(isEmptyObj([]))//false
console.log(isEmptyObj(1))//false
console.log(isEmptyObj(""))//false
console.log(isEmptyObj(null))//false
console.log(isEmptyObj(undefined))//false

参考