Skip to content

JavaScript专题之类型判断(上) #28

Description

@mqyqingfeng

前言

类型判断在 web 开发中有非常广泛的应用,简单的有判断数字还是字符串,进阶一点的有判断数组还是对象,再进阶一点的有判断日期、正则、错误类型,再再进阶一点还有比如判断 plainObject、空对象、Window 对象等等。

以上都会讲,今天是上半场。

typeof

我们最最常用的莫过于 typeof,注意,尽管我们会看到诸如:

console.log(typeof('yayu')) // string

的写法,但是 typeof 可是一个正宗的运算符,就跟加减乘除一样!这就能解释为什么下面这种写法也是可行的:

console.log(typeof 'yayu') // string

引用《JavaScript权威指南》中对 typeof 的介绍:

typeof 是一元操作符,放在其单个操作数的前面,操作数可以是任意类型。返回值为表示操作数类型的一个字符串。

那我们都知道,在 ES6 前,JavaScript 共六种数据类型,分别是:

Undefined、Null、Boolean、Number、String、Object

然而当我们使用 typeof 对这些数据类型的值进行操作的时候,返回的结果却不是一一对应,分别是:

undefined、object、boolean、number、string、object

注意以上都是小写的字符串。Null 和 Object 类型都返回了 object 字符串。

尽管不能一一对应,但是 typeof 却能检测出函数类型:

function a() {}

console.log(typeof a); // function

所以 typeof 能检测出六种类型的值,但是,除此之外 Object 下还有很多细分的类型呐,如 Array、Function、Date、RegExp、Error 等。

如果用 typeof 去检测这些类型,举个例子:

var date = new Date();
var error = new Error();
console.log(typeof date); // object
console.log(typeof error); // object

返回的都是 object 呐,这可怎么区分~ 所以有没有更好的方法呢?

Object.prototype.toString

是的,当然有!这就是 Object.prototype.toString!

那 Object.protototype.toString 究竟是一个什么样的方法呢?

为了更加细致的讲解这个函数,让我先献上 ES5 规范地址:https://es5.github.io/#x15.2.4.2。

在第 15.2.4.2 节讲的就是 Object.prototype.toString(),为了不误导大家,我先奉上英文版:

When the toString method is called, the following steps are taken:

  1. If the this value is undefined, return "[object Undefined]".
  2. If the this value is null, return "[object Null]".
  3. Let O be the result of calling ToObject passing the this value as the argument.
  4. Let class be the value of the [[Class]] internal property of O.
  5. Return the String value that is the result of concatenating the three Strings "[object ", class, and "]".

凡是规范上加粗或者斜体的,在这里我也加粗或者斜体了,就是要让大家感受原汁原味的规范!

如果没有看懂,就不妨看看我理解的:

当 toString 方法被调用的时候,下面的步骤会被执行:

  1. 如果 this 值是 undefined,就返回 [object Undefined]
  2. 如果 this 的值是 null,就返回 [object Null]
  3. 让 O 成为 ToObject(this) 的结果
  4. 让 class 成为 O 的内部属性 [[Class]] 的值
  5. 最后返回由 "[object " 和 class 和 "]" 三个部分组成的字符串

通过规范,我们至少知道了调用 Object.prototype.toString 会返回一个由 "[object " 和 class 和 "]" 组成的字符串,而 class 是要判断的对象的内部属性。

让我们写个 demo:

console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]

var date = new Date();
console.log(Object.prototype.toString.call(date)) // [object Date]

由此我们可以看到这个 class 值就是识别对象类型的关键!

正是因为这种特性,我们可以用 Object.prototype.toString 方法识别出更多类型!

那到底能识别多少种类型呢?

至少 12 种!

你咋知道的?

我数的!

……

让我们看个 demo:

// 以下是11种:
var number = 1;          // [object Number]
var string = '123';      // [object String]
var boolean = true;      // [object Boolean]
var und = undefined;     // [object Undefined]
var nul = null;          // [object Null]
var obj = {a: 1}         // [object Object]
var array = [1, 2, 3];   // [object Array]
var date = new Date();   // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g;          // [object RegExp]
var func = function a(){}; // [object Function]

function checkType() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)

除了以上 11 种之外,还有:

console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]

除了以上 13 种之外,还有:

function a() {
    console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
a();

所以我们可以识别至少 14 种类型,当然我们也可以算出来,[[class]] 属性至少有 12 个。

type API

既然有了 Object.prototype.toString 这个神器!那就让我们写个 type 函数帮助我们以后识别各种类型的值吧!

我的设想:

写一个 type 函数能检测各种类型的值,如果是基本类型,就使用 typeof,引用类型就使用 toString。此外鉴于 typeof 的结果是小写,我也希望所有的结果都是小写。

考虑到实际情况下并不会检测 Math 和 JSON,所以去掉这两个类型的检测。

我们来写一版代码:

// 第一版
var class2type = {};

// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error Null Undefined".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}

嗯,看起来很完美的样子~~ 但是注意,在 IE6 中,null 和 undefined 会被 Object.prototype.toString 识别成 [object Object]!

我去,竟然还有这个兼容性!有什么简单的方法可以解决吗?那我们再改写一版,绝对让你惊艳!

// 第二版
var class2type = {};

// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
    class2type["[object " + item + "]"] = item.toLowerCase();
})

function type(obj) {
    // 一箭双雕
    if (obj == null) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj)] || "object" :
        typeof obj;
}

isFunction

有了 type 函数后,我们可以对常用的判断直接封装,比如 isFunction:

function isFunction(obj) {
    return type(obj) === "function";
}

数组

jQuery 判断数组类型,旧版本是通过判断 Array.isArray 方法是否存在,如果存在就使用该方法,不存在就使用 type 函数。

var isArray = Array.isArray || function( obj ) {
    return type(obj) === "array";
}

但是在 jQuery v3.0 中已经完全采用了 Array.isArray。

结语

到此,类型判断的上篇就结束了,我们已经可以判断日期、正则、错误类型啦,但是还有更复杂的判断比如 plainObject、空对象、Window对象、类数组对象等,路漫漫其修远兮,吾将上下而求索。

哦, 对了,这个 type 函数抄的 jQuery,点击查看 type 源码。

专题系列

JavaScript专题系列目录地址:https://github.com/mqyqingfeng/Blog。

JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

Activity

  1. HerryBin commented on Jul 4, 2017

    @HerryBin

    博主,这句哈撒意思?ES6 是新增了 Symbol 类型,博主是否写错了?
    " 然而当我们使用 typeof 对这些数据类型的值进行操作的时候,返回的结果却不是一一对应,分别是:
    undefined、object、boolean、number、string、object "

    加油写,我会一直看的。

  2. mqyqingfeng commented on Jul 4, 2017

    @mqyqingfeng
    OwnerAuthor

    @HerryBin 感谢支持~ 正在努力撰写中……

    default

    想表达的意思就是对上面六种数据类型的值进行 typeof 运算,结果分别对应下面的值

    typeof undefined // "undefined"
    typeof null // "object"
    typeof true // "boolean"
    typeof 1 // "number"
    typeof "s" // "string"
    typeof {} // "object"
  3. JJL-SH commented on Aug 24, 2017

    @JJL-SH

    我想问下这里 或 后面的object在什么情况下会发生
    class2type[Object.prototype.toString.call(obj)] || 'object'

  4. mqyqingfeng commented on Aug 24, 2017

    @mqyqingfeng
    OwnerAuthor

    @wittybob 比如说 ES6 新增的 Symbol、Map、Set 等类型,它们并不在 class2type 列表中,所以使用 type 函数,返回的结果会是 object

  5. JJL-SH commented on Aug 24, 2017

    @JJL-SH

    @mqyqingfeng 酷炫屌炸天

  6. wangdabaoqq commented on Oct 26, 2017

    @wangdabaoqq

    关于结果是小写哪里如果不写这段

    "Boolean Number String Function Array Date RegExp Object Error Null Undefined".split(" ").map(function(item, index) {
        class2type["[object " + item + "]"] = item.toLowerCase();
    })

    如数组打印出来它会是大写的吗?

  7. mqyqingfeng commented on Oct 26, 2017

    @mqyqingfeng
    OwnerAuthor

    @wangdabaoqq 正常调用 Object.prototype.toString.call(obj) 的时候,以 obj 为一个日期类型为例的话,结果会是 [object Date],不是全大写,而是首字母大写,需求是希望结果是 "date"

  8. aikilan commented on Oct 26, 2017

    @aikilan
    function detectType(val){
    	return Object.prototype.toString.call(val).split(' ')[1].split(']')[0].toLowerCase();
    }

    不考虑兼容性的话,这样写有其他的问题吗?

  9. mqyqingfeng commented on Oct 26, 2017

    @mqyqingfeng
    OwnerAuthor

    @xu824332362 可以呀,你也可以用正则匹配出来~

  10. cp-apple commented on Oct 31, 2017

    @cp-apple

    var isArray = Array.isArray || function( obj ) {
    return type(obj) === "array";
    }
    Array.isArray是不是少带了一个参数... Array.isArray(obj)

  11. mqyqingfeng commented on Oct 31, 2017

    @mqyqingfeng
    OwnerAuthor

    @cp-apple Array.isArray 是一个判断,意思是如果有 Array.isArray 这个方法,isArray 就等于这个方法,如果没有才使用后面这个函数,注意,这里的 isArray 是一个函数,如果加上 Array.isArray(obj),如果 obj 不是数组,isArray 的值就等于 false 了

  12. cp-apple commented on Nov 1, 2017

    @cp-apple

    @mqyqingfeng 噢噢,这样啊,谢谢。o.o

  13. merzoo commented on Nov 6, 2017

    @merzoo

    Object.prototype.toString 标题和第一行 Object 拼错啦~ up 主 ~ (-c-

  14. mqyqingfeng commented on Nov 8, 2017

    @mqyqingfeng
    OwnerAuthor

    @merzoo 哈哈,感谢细心的 merzoo 指出~ o( ̄▽ ̄)d

  15. 28 remaining items

  16. naaaacI-Y commented on Mar 10, 2020

    @naaaacI-Y

    function type(obj) {
    let toString = Object.prototype.toString
    if(obj == null) {
    return String(obj)
    }
    return typeof obj === 'object' ?
    toString.call(obj).slice(8,toString.call(obj).length - 1).toLowerCase() : typeof obj
    }
    一种简便的写法

  17. guifengjin commented on Apr 13, 2020

    @guifengjin
    function checkType(target) {
        return Object.prototype.toString.call(target).slice(8, -1)
    }
  18. RoeyXie commented on Jun 5, 2020

    @RoeyXie

    var promise = new Promise(
    function() {}
    ) // [object Promise]

  19. KeithChou commented on Jun 14, 2020

    @KeithChou

    个人感觉jQuery这里做的不太好的就是,判断数据类型的type函数的扩展性不太好
    类似像window location navigator document都会返回object啦 就不够精确
    这里给出另外一个实现哈

    const type = (obj?: any) => {
        if (typeof obj !== 'object' || typeof obj === 'function') {
            return typeof obj
        } else {
            return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase() 
        }
    }
    

    这个方法取Object.prototype.toString.call(obj)的构造函数名称的部分 然后直接转换成小写 就可以针对各种各样的构造函数啦

  20. dancingTx commented on Jun 16, 2020

    @dancingTx

    @wittybob 比如说 ES6 新增的 Symbol、Map、Set 等类型,它们并不在 class2type 列表中,所以使用 type 函数,返回的结果会是 object

    symbol 不是基础类型吗?应该会走typeof 那一个吧

  21. ZhengMengSi commented on Aug 17, 2020

    @ZhengMengSi

    image

  22. Vuact commented on Dec 4, 2020

    @Vuact

    type API 个人感觉这么写更好些~

    const typeMap = "Symbol Boolean Number String Function Array Date RegExp Object Error"
      .split(" ")
      .reduce((prev, item) => {
        prev[`[object ${item}]`] = item.toLowerCase();
        return prev;
      }, {});
    
    function type(obj) {
      //解决IE6 中的兼容
      if(null == obj) return '' + obj;
    
      return typeof obj !== "object" 
        ? typeof obj
        : typeMap[Object.prototype.toString.call(obj)] || 'object';
    }
  23. cole31415 commented on Dec 22, 2020

    @cole31415

    文中"那Object.protototype.toString 究竟是一个什么样的方法呢",这里多写了个to

  24. lonfger commented on Apr 9, 2021

    @lonfger
    function type(obj) {
        // 一箭双雕
        if (obj == null) {
            return obj + "";
        }
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[Object.prototype.toString.call(obj)] || "object" :
            typeof obj;
    }

    这个地方有点疑惑,typeOf不是能直接检测出来为function类型吗?而且自己测试了在ie8+中是没有兼容问题的。那么为什么还需要通过Object.prototype.toString.call(obj)这一段进行function的类型检测呢?楼主。。。。是有什么其他的原因吗?

  25. ChenKun1997 commented on Oct 13, 2021

    @ChenKun1997

    大佬,我想问下明明Object.prototype.toString.call()已经可以检测出来所有这些类型了(包括基本类型),为什么还要去区分基本类型然后使用typeof呢?直接都是用Object.prototype.toString.call()不就好了吗

  26. martisss commented on Oct 22, 2021

    @martisss

    function type(obj) {
    return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
    }

  27. dagedan commented on Nov 8, 2021

    @dagedan

    "嗯,看起来很完美的样子~~ 但是注意,在 IE6 中,null 和 undefined 会被 Object.prototype.toString 识别成 [object Object]!
    " 这句话是不对的, Object.prototype.toString.call(undefined) 返回的不是[object Object],建议修正一下

  28. wl05 commented on Feb 8, 2022

    @wl05

    大佬,我想问下明明Object.prototype.toString.call()已经可以检测出来所有这些类型了(包括基本类型),为什么还要去区分基本类型然后使用typeof呢?直接都是用Object.prototype.toString.call()不就好了吗

    typeof 效率更高

  29. MagicalBridge commented on Apr 20, 2022

    @MagicalBridge

    学习的时候,得留下印记,不然今后如何感慨岁月如梭呢,转眼间,已经关注大佬博客3年了

  30. YuFengjie97 commented on Dec 14, 2024

    @YuFengjie97
    const o = { name: 11 }
    class Person {
      name = ''
      constructor() {
        this.name = 11
      }
    }
    const p = new Person()
    const str = 'dddd'
    const num = 111
    const bool = true
    const d = new Date()
    const f = function () { }
    const m = new Map()
    const s = new Set()
    const wm = new WeakMap()
    const arr = [1,2,3]
    
    
    function type(obj) {
      if (obj === null) return 'null'
      // if (Array.isArray(obj)) return 'Array'
      const a = obj.constructor.name
      const b = Object.prototype.toString.call(obj).slice(8, -1)
      console.log(a, '---', b);
    }
    
    type(null)
    type(o)
    type(p)
    type(str)
    type(num)
    type(bool)
    type(d)
    type(f)
    type(m)
    type(s)
    type(wm)
    type(arr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions