likes
comments
collection
share

检测数据类型的通用方法

作者站长头像
站长
· 阅读数 38

前言

日常工作中你还只会使用typeof判断数据类型?使用typeof、instanceof、constructor这些方法判断数据类型都不够全面,而使用Object.prototype.toString.call()几乎没有局限,是检测数据类型相对准确的方式,但是返回结果不够简洁,那么基于 Object.prototype.toString.call() 封装一个检测 JavaScript 数据类型的通用方法就很有必要啦。

几种检测方法

原始类型:null、undefined、number、string、boolean、symbol、bigInt

1、typeof 操作符

  • 'undefined' - 这个值未定义(未初始化/未声明)
  • 'boolean' - 这个值为布尔值
  • 'string' - 这个值是字符串
  • 'number' - 这个值是数值
  • 'object' - 这个值是对象或null
  • 'function' - 这个值是函数(函数确实有一些特殊性,故typeof区分函数和对象是有必要的)
typeof 1; // 'number'
typeof 'a';  // 'string'
typeof true; // 'boolean'
typeof Symbol('a'); // 'symbol'
typeof 11n; // 'bigint'
typeof function () {}; // 'function'
typeof undefined; // 'undefined'
typeof {}; // 'object'
typeof null; // 'object' 因js中二进制前三位都为0会被判断为object类型,而null的二进制表示全为0
typeof [0, 1, 2]; // 'object'
typeof /a/; // 'object'
typeof new Error(); // 'object'
typeof new Map(); // 'object'
typeof new Set(); // 'object'

结论:typeof 可用于检测除 null 以外的原始类型,但引用类型除 Function 外均会返回object

  • null是一个空指针对象,如果将来用于保存对象,最好将该对象初始化为null
  • 实际上undefined派生至null值,故null == undefined; // true
  • typeof NaN; // number

2、 instanceof

instanceof 运算符 用于检测构造函数的 prototype 属性是否出现在某个 实例对象 的原型链上

object instanceof constructor  //(检测对象是Object)

示例:

function Parent() {}
function Children() {}
Children.prototype.__proto__ = Parent.prototype; // 继承

const obj = new Children();
console.log(obj instanceof Children );   // true
console.log(obj instanceof Parent );   // true

源码分析:

function instance_of(L, R) { // L表示对象实例,R表示构造函数或者父类型实例
    var O = R.prototype; 
    L = L.__proto__;
    while (true) {    
        if (L === null) // 原型链的最终指向是null
             return false;   
        if (O === L) 
             return true;   
        L = L.__proto__;  
    }
}

instanceof 可以用于检测引用数据类型,不能检测原始类型;另外还需注意改变原型链指向后,使用此方法判断是不准确的

有没有办法实现 instanceof 检测原始类型呢?Symbol.hasInstance

3、 constructor

constructor 是一种用于创建和初始化 class 创建的对象的特殊方法。每一个实例对象都可通过constructor来访问它的构造函数。

// 官方示例
class Polygon {
  constructor() {
    this.name = 'Polygon';
  }
}
const poly1 = new Polygon();
console.log(poly1.constructor === Polygon); // true

console.log('1'.constructor === String); // true
console.log(true.constructor === Boolean); // true
console.log(1.constructor === Number); // Uncaught SyntaxError 错误来自于浮点数的字面量解析过程
console.log((1).constructor === Number); // true  小括号运算符能够把数值转换为对象
console.log(1.0.constructor === Number); // true
console.log(1..constructor === Number); // true 浮点数的小数位是可以省略
console.log(true.constructor === Boolean); // true
console.log([].constructor === Array); // true
console.log({}.constructor === Object); // true
console.log(Symbol('1').constructor === Symbol); // true
console.log(new Date().constructor === Date); // true
console.log(11n.constructor === BigInt); // true
console.log(/a/.constructor === RegExp); // true
const f = () => {};
console.log(f.constructor === Function); // true
console.log(null.constructor); // Uncaught TypeError: Cannot read properties of null
console.log(undefined.constructor); // Uncaught TypeError: Cannot read property 'constructor' of undefined

constructor 除 null 和 undefined 外能用于检测js的原始类型和引用类型; 但当对象的原型更改之后,使用此方法判断也是不准确的(这时就需要借助__proto__来判断了)

4、Object.prototype.toString

toString 返回一个【表示对象】的【字符串】

每个对象都有一个 toString()方法。 默认的情况下,toString()方法会被每一个对象继承,若toString没有被定义的对象覆盖,那么toString会返回 '[object type]' 

console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call('a')); // [object String]
console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(Symbol('a'))); // [object Symbol]
console.log(Object.prototype.toString.call(11n)); // [object BigInt]
console.log(Object.prototype.toString.call(/a/)); // [object RegExp]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call([0, 1, 2])); // [object Array]
console.log(Object.prototype.toString.call(function () {})); // [object Function]
console.log(Object.prototype.toString.call(new Error())); // [object Error]
console.log(Object.prototype.toString.call(new Map())); // [object Map]
console.log(Object.prototype.toString.call(new Set())); // [object Set]

基于返回结果'[object type]' ,我们可以使用 正则匹配或截取 的方式将结果处理为类似typeof的返回结果方便我们日常的使用(注意大小写)。

最佳实践

// 截取
function getDataType(object) {
  return Object.prototype.toString.call(object).slice(8, -1).toLowerCase();
}

// 正则匹配
function getDataType(object) {
  return Object.prototype.toString.call(object).replace(/^\[object (\S+)\]$/, '$1').toLowerCase();
}

示例:

console.log(getDataType({})); // object
console.log(getDataType('a')); // string
console.log(getDataType(1)); // number
console.log(getDataType(true)); // boolean
console.log(getDataType(null)); // null
console.log(getDataType(undefined)); // undefined
console.log(getDataType(Symbol('a'))); // symbol
console.log(getDataType(11n)); // bigint
console.log(getDataType(/a/)); // regexp
console.log(getDataType(new Date())); // date
console.log(getDataType([0, 1, 2])); // array
console.log(getDataType(function () {})); // function
console.log(getDataType(new Error())); // error
console.log(getDataType(new Map())); // map
console.log(getDataType(new Set())); // set

参考文献

MDN - instanceof

MDN - constructor

假如易立竞问你如何判断 JavaScript 中的数据类型?