Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
mqyqingfeng opened this issue Jun 27, 2017 · 57 comments
Open

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

mqyqingfeng opened this issue Jun 27, 2017 · 57 comments

Comments

@mqyqingfeng
Copy link
Owner

mqyqingfeng commented Jun 27, 2017

前言

类型判断在 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,对作者也是一种鼓励。

@HerryBin
Copy link

HerryBin commented Jul 4, 2017

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

加油写,我会一直看的。

@mqyqingfeng
Copy link
Owner Author

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

default

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

typeof undefined // "undefined"
typeof null // "object"
typeof true // "boolean"
typeof 1 // "number"
typeof "s" // "string"
typeof {} // "object"

@JJL-SH
Copy link

JJL-SH commented Aug 24, 2017

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

@mqyqingfeng
Copy link
Owner Author

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

@JJL-SH
Copy link

JJL-SH commented Aug 24, 2017

@mqyqingfeng 酷炫屌炸天

@wangdabaoqq
Copy link

wangdabaoqq commented Oct 26, 2017

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

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

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

@mqyqingfeng
Copy link
Owner Author

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

@xukechu
Copy link

xukechu commented Oct 26, 2017

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

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

@mqyqingfeng
Copy link
Owner Author

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

@cp-apple
Copy link

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

@mqyqingfeng
Copy link
Owner Author

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

@cp-apple
Copy link

cp-apple commented Nov 1, 2017

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

@merzoo
Copy link

merzoo commented Nov 6, 2017

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

@mqyqingfeng
Copy link
Owner Author

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

@ClarenceC
Copy link

记忆中还有一种判定方法是 instanceof
网上查了一下原来还有 constructor
一共4 种.

@mqyqingfeng
Copy link
Owner Author

@ClarenceC 确实的,基本是四种方法:

  1. typeof
  2. constructor
  3. instanceof
  4. Object.prototype.toString

对于数组类型,还有 Array.isArray 可以用于判断~

@Tan90Qian
Copy link

看了类型判断的下一篇,里面讲到window的时候,大佬用了一段obj === obj.window。直接让我惊呆了,js的对象居然还能用===来判断的?然后去测试了下,很震惊的发现,诸如windowlocationhistory这样的浏览器相关的对象,Object.prototype.toString.call方法都是单独识别出来。。。这还是头一次发现orz而且它们全都能通过===来和自身相等,当然这一点应该是因为内存中这些浏览器相关对象都只有一份。
由此也学到了一个知识点:js中的===并非对所有引用类型的数据进行判断时都为false,对于那些接近“单例”形式的对象,是可以和自身全等的。(知识点get)

@mqyqingfeng
Copy link
Owner Author

@Tan90Qian 厉害了,我也才发现,原来:

Object.prototype.toString.call(location)
// "[object Location]"
Object.prototype.toString.call(history)
// "[object History]"

而且他们跟自身相等是因为引用是相同的吧,就像:

var obj = {};
console.log(obj === obj) // true

@Tan90Qian
Copy link

@mqyqingfeng 对,是因为“自身和自身的引用相同”,但其实这个思路其实在写逻辑判断的时候非常少用吧?正常的情况是,基本不会在对引用类型进行逻辑判断的时候直接对数据本身使用===,而是借助于某些特性(比如type、length),所以之前看到那段代码的时候吃了一惊。毕竟window肯定是引用类型的,没想起自身相等这回事(遇到这种场景的次数太少了)。

PS:博主大大有空去看下bind的那一篇,感觉有些问题。

@mqyqingfeng
Copy link
Owner Author

@Tan90Qian bind 那篇有回复,确实是有问题的,我也发现了这一点,不过想到既然 MDN 实现如此,那我也讲到这种程度吧……完整的实现确实会更繁琐一点

@Tan90Qian
Copy link

@mqyqingfeng 看到了,之前邮箱一直没有邮件提醒,我还以为dalao最近没空。从月初开始,每天看2、3篇博客,顺便“找茬”,收获非常大~

@id77
Copy link

id77 commented Jun 24, 2019

typeof BigInt(1)
"bigint"

Object.prototype.toString.call(BigInt(1))
"[object BigInt]"

@tan215210976
Copy link

我想问下Object的valueOf和toString的区别在哪

@KGKAI
Copy link

KGKAI commented Aug 29, 2019

把map换成forEach是否更合理一些? @mqyqingfeng

@bowencool
Copy link

请问下如何识别Proxy类型
image

@sunchengzhou
Copy link

请问下如何识别Proxy类型
image

代理的值类型就是代理的那个值的类型

@yangtao2o
Copy link

试了一下新增的其他类型:

var symb = Symbol('test'); // [object Symbol]
var set = new Set();       // [object Set]
var map = new Map();       // [object Map]
var bigI = BigInt(1);      // [object BigInt]

加起来有 15 种了,乖乖~

@naaaacI-Y
Copy link

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
}
一种简便的写法

@guifengjin
Copy link

function checkType(target) {
    return Object.prototype.toString.call(target).slice(8, -1)
}

@RoeyXie
Copy link

RoeyXie commented Jun 5, 2020

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

@KeithChou
Copy link

个人感觉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)的构造函数名称的部分 然后直接转换成小写 就可以针对各种各样的构造函数啦

@dancingTx
Copy link

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

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

@ZhengMengSi
Copy link

image

@Vuact
Copy link

Vuact commented Dec 4, 2020

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';
}

@cole31415
Copy link

cole31415 commented Dec 22, 2020

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

@dogfeeling
Copy link

请问一下怎么理解 typeof 函数 = function 而不是 object

@lonfger
Copy link

lonfger commented Apr 9, 2021

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的类型检测呢?楼主。。。。是有什么其他的原因吗?

@ChenKun1997
Copy link

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

@martisss
Copy link

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

@dagedan
Copy link

dagedan commented Nov 8, 2021

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

@wl05
Copy link

wl05 commented Feb 8, 2022

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

typeof 效率更高

@MagicalBridge
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests