前言

当我们封装一些工具类方法,需要对参数类型严格判断的时候,往往会用到类型判断,下面我介绍几种自己实用的类型判断方式

一、数据类型有那些

在ECMAScript规范中,共定义了7种数据类型,对于它的分法也是多种多样,下面我总结了一种简单清晰的分类方法:(注:Bigint将会成为一种新增数据类型,这里我们暂不介绍)
  1. 基本类型(简单类型/值类型/原始类型):布尔、数值、字符串、undefined、null、Symbol(ES6 新增)
  2. 引用类型(复杂类型):Object
    • 基本包装对象:Boolean、Number、String
    • 单体内置对象:Array、Function、Date、RegExp
    • 宿主环境对象:浏览器环境-HTMLDocument、Window;Node环境-global等

二、 数据类型判断方法

1.typeof

typeof 返回值 “object” 、“number”、“boolean”、“undefined”、“function” 、“string”、“function”、'symbol"

typeof 返回的值都是字符串类型

typeof 操作可以判断基本类型的数据,但是也存在一些特例,比如 typeof null 返回的是“object” ,因为 从逻辑上,null 这个特殊值被认为是一个对空对象的引用,表示一个空对象指针,实际上是基础类型

typeof 5          // "number"
typeof true       // "boolean"
typeof 'text'     // "string"
typeof Symbol(1)  // "symbol"
typeof undefined  // "undefined"
typeof null       // "object"
注意:因为 typeof 是一个操作符而不是函数,所以不需要参数,但是可以使用参数,执行结果是一样的

let message = "some string"
typeof message  // "string"
typeof(message) // "string"

对于引用类型的数据,typeof 判断的结果均为 “object”, 函数返回 “function” symbol 返回 ‘symbol’

typeof []             // "object"
typeof new Date()     // "object"
typeof function(){}   // "function"
typeof symbol("xx")   // "symbol"

小结:typeof 只能准确判断除 null 以外的基本类型

2.instanceof

instanceof 是用来 判断数据是否是某个对象的实例,返回一个布尔值

基本用法

// 判断 obj 是否为 Object 的实例
function Person(name) {
 this.name = name
}
const p = new Person('sunshine')
p instanceof Person // true

// 这里的 p 是 Person 函数构造出来的,所以顺着 p 的原型链可以找到Object的构造函数
p.__proto__ === Person.prototype // true
p.__proto__.__proto__ === Object.prototype // true

缺点

  • 对于基本类型的数据,instanceof是不能直接判断它的类型的,因为实例是一个对象或函数创建的,是引用类型,所以需要通过基本类型对应的 包装对象 来判断。所以对于 null 和 undefined 这两个家伙就检测不了了~
5 instanceof Number // false
new Number(5) instanceof Number  // true
  • 因为原型链继承的关系,instanceof 会把数组都识别为 Object 对象,所有引用类型的祖先都是 Object 对象
let arr = [1, 2]
console.log(Object.prototype.toString.call(arr) === '[object Array]') // true
console.log(arr instanceof Array) // true
console.log(arr instanceof Object) // true
let fn = function(){}
console.log(fn instanceof Object)  // true

手写实现 instanceof

instanceof的核心原理是:用来检测某个实例对象的原型链上是否存在构造函数的 prototype 属性

function myInstanceof(instanceObj, constructorFun) {
  const prototypeObj = constructorFun.prototype // 获取构造函数的原型对象(显示原型)
  instanceObj = instanceObj.__proto__ // 获取实例对象的原型(隐式原型)
  // 循环进行查找原型 直到 Object.prototype.__proto__  === null
  while (instanceObj) {
    if (prototypeObj === instanceObj) {
      return true
    }
    instanceObj = instanceObj.__proto__ // 重点(核心):层层向上遍历
  }
  return false
}

myInstanceof('', String) // true
myInstanceof(1, String) // false

function Person(name) {
 this.name = name
}
const p = new Person('sunshine')
myInstanceof(p, Person) // true

小结

  • instanceof 可以检测所有能转换为实例对象的数据
  • 所有引用类型都是 Object 的实例

3.constructor

使用 constructor 可以查看目标构造函数,也可以进行数据类型判断。但是不能判断 null 和 undefined,因为这两个特殊类型没有其对应的包装对象。constructor和instanceof 类似,constructor 返回结果的是自己的构造函数,而 instructor 则是自己与构造函数比较返回布尔值

(5).constructor === Number     // true
"text".constructor === String  // true
true.constructor === Boolean   // true
({}).constructor === Object    // true
console.log({} instanceof Object) // true

// Uncaught TypeError: Cannot read property 'constructor' of undefined
undefined.constructor === undefined  // 报错
null.constructor === null            // 报错

4.Object.prototype.toString

在判断数据类型时,我们称 Object.prototype.toString 为 “万能方法” “终极方法”,工作中也是比较常用而且准确

// 简单实现
var arr = [1, 2, 3]
Object.prototype.toString.call(arr)

// 封装实现 (返回对应类型)
function type(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}

// 简易实现 Array.isArray() 方法
function isArray(obj) {
  return type(obj) === 'array'
}
console.log(isArray(arr)) // true

深度剖析

在ECMAScript中,Object类型的每个实例都有 toString() 方法,返回对象的字符串表示,所以每个实例化的对象都可以调用 toString() 方法,并且其他数据类型也能使用 toString()

var obj = {a: 1}
console.log(obj.toString())     //"[object Object]"

var flag = true
console.log(flag.toString())     // "true"

var num = 1
console.log(num.toString());    // "1"

那么为什么都可以使用 toString() 方法,方法是哪里来?

我们顺着原型链,obj => obj.proto => Object.prototype,可以发现,toString()方法是定义在 Object 的原型对象 Object.prototype上的,这样 Object 的每个实例化对象都可以共享Object.prototype.toString()方法

如果不通过原型链查找,直接调用Object.prototype.toString()也可以,因为Object.prototype 也是对象,所以返回了对象的字符串表示。通过obj对象调用Object.prototype.toString()方法

我们再来分析一下不同类型的“对象”调用toString()方法,返回值有什么不同之处?

Object.prototype.toString.call/apply(obj) // "[object Object]"

Object 作为引用类型,它是一种数据结构,常被称为 Object类(但这种称呼并不妥当,JS中没有类,一切都是语法糖而已)。另外,基于Object 类型,JS还实现了其他常用的对象子类型(就是不同类型的对象),我们可以说,Object类是所有子类的父类(祖先)

上面提到的定义在 Object.prototype 上的 toString() 方法,可以说是最原始的toString()方法了,其他类型都或多或少重写了toString()方法,导致不同类型的对象调用 toString() 方法产生返回值各不相同

Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(1) // "[object Number]"
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() // "[object Undefined]"
Object.prototype.toString.call(new Date()) // "[object Date]"
Object.prototype.toString.call(/at/) // "[object RegExp]"

注意:纯数字不可以直接调用toString方法,因为.也可以代表数学中的小数点,js 执行引擎在做语法解析的时候,会通过.分隔符来解析上下文,但是对于 1.toString() 这样的表达式,会理解为不合理的小数,故而报错。如果想解决这个问题,可以采用以下形式,即 (1).toString()、1…toString()、1 .toString()

1.toString()      // Uncaught SyntaxError: Invalid or unexpected token
(1).toString()    // "1"
1..toString()     // "1"
1 .toString()     // "1"

小结

toString()方法是定义在 Object 的原型对象 Object.prototype上的,Object的每个实例化对象都可以共享Object.prototype.toString()方法,所以说Object.prototype.toString.call(xxx) 是类型判断的终极解决方案

5.练习

通过上面的几种判断数据类型学习,以下练习一下吧

// 如何判断一个对象是否为数组?

// 1.判断是否属于数组实例
[] instanceof Array === true

// 2. 通过对象的原型方法判断
Object.prototype.toString.call(arr) // es3

// 3. 判断值是不是数组 (数组身上判断方法)
Array.isArray([])

// 4. constructor
[].constructor === Array

6.应用

// Iview源码
function typeOf(obj) {
    const toString = Object.prototype.toString;
    const map = {
        '[object Boolean]'  : 'boolean',
        '[object Number]'   : 'number',
        '[object String]'   : 'string',
        '[object Function]' : 'function',
        '[object Array]'    : 'array',
        '[object Date]'     : 'date',
        '[object RegExp]'   : 'regExp',
        '[object Undefined]': 'undefined',
        '[object Null]'     : 'null',
        '[object Object]'   : 'object'
    };
    return map[toString.call(obj)];
}
// element-ui源码
export function isString(obj) {
  return Object.prototype.toString.call(obj) === '[object String]';
}

export function isObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]';
}

export function isHtmlElement(node) {
  return node && node.nodeType === Node.ELEMENT_NODE;
}

export const isFunction = (functionToCheck) => {
  var getType = {};
  return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
};

export const isUndefined = (val)=> {
  return val === void 0;
};

export const isDefined = (val) => {
  return val !== undefined && val !== null;
};


总结

判断数据类型方法有很多,实际使用需要根据自己的需求使用最适合自己的方法

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐