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 · 42 comments
Open

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

mqyqingfeng opened this issue Jun 27, 2017 · 42 comments
Labels

Comments

@mqyqingfeng
Copy link
Owner

@mqyqingfeng 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

This comment has been minimized.

Copy link

@HerryBin HerryBin commented Jul 4, 2017

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

加油写,我会一直看的。

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jul 4, 2017

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

default

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

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

This comment has been minimized.

Copy link

@WittyBob WittyBob commented Aug 24, 2017

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

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Aug 24, 2017

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

@WittyBob

This comment has been minimized.

Copy link

@WittyBob WittyBob commented Aug 24, 2017

@mqyqingfeng 酷炫屌炸天

@wangdabaoqq

This comment has been minimized.

Copy link

@wangdabaoqq 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

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Oct 26, 2017

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

@xu824332362

This comment has been minimized.

Copy link

@xu824332362 xu824332362 commented Oct 26, 2017

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

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

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Oct 26, 2017

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

@cp-apple

This comment has been minimized.

Copy link

@cp-apple cp-apple commented Oct 31, 2017

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

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Oct 31, 2017

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

@cp-apple

This comment has been minimized.

Copy link

@cp-apple cp-apple commented Nov 1, 2017

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

@merzoo

This comment has been minimized.

Copy link

@merzoo merzoo commented Nov 6, 2017

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

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Nov 8, 2017

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

@ClarenceC

This comment has been minimized.

Copy link

@ClarenceC ClarenceC commented Dec 28, 2017

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

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jan 5, 2018

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

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

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

@Tan90Qian

This comment has been minimized.

Copy link

@Tan90Qian Tan90Qian commented Apr 15, 2018

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

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Apr 16, 2018

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

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

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

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

This comment has been minimized.

Copy link

@Tan90Qian Tan90Qian commented Apr 16, 2018

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

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

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Apr 16, 2018

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

@Tan90Qian

This comment has been minimized.

Copy link

@Tan90Qian Tan90Qian commented Apr 16, 2018

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

@mqyqingfeng

This comment has been minimized.

Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Apr 16, 2018

@Tan90Qian 哈哈,欢迎接着“找茬”~

@Angelki

This comment has been minimized.

Copy link

@Angelki Angelki commented Oct 8, 2018

image
看来我还是没懂原型链 。。。

@mohcanohca

This comment has been minimized.

Copy link

@mohcanohca mohcanohca commented Oct 12, 2018

image
看来我还是没懂原型链 。。。
Object.toString是将Object作为一个对象访问,Object自身没有属性toString,toString在Object的原型链上,Object.proto===Funtion.prototype,toString是Function.prototype的一个属性
image
MDN中说,Function 对象覆盖了从 Object 继承来的 Object.prototype.toString 方法。函数的 toString 方法会返回一个表示函数源代码的字符串。具体来说,包括 function关键字,形参列表,大括号,以及函数体中的内容。若 this 不是 Function 对象,则 toString() 方法将抛出 TypeError异常
image
不知道有没有对楼上的问题有一点帮助,如果有不对的地方,欢迎指出~

@Angelki

This comment has been minimized.

Copy link

@Angelki Angelki commented Oct 17, 2018

image
看来我还是没懂原型链 。。。
Object.toString是将Object作为一个对象访问,Object自身没有属性toString,toString在Object的原型链上,Object.proto===Funtion.prototype,toString是Function.prototype的一个属性
image
MDN中说,Function 对象覆盖了从 Object 继承来的 Object.prototype.toString 方法。函数的 toString 方法会返回一个表示函数源代码的字符串。具体来说,包括 function关键字,形参列表,大括号,以及函数体中的内容。若 this 不是 Function 对象,则 toString() 方法将抛出 TypeError异常
image
不知道有没有对楼上的问题有一点帮助,如果有不对的地方,欢迎指出~

虽然我已经查过了,但是你的回答还是非常棒的。

@cobish

This comment has been minimized.

Copy link

@cobish cobish commented Oct 24, 2018

console.log(typeof Null);  // undefined

的结果应该是 undefined,而不是 object 吧?

image


哦是我搞错了,应该是

console.log(typeof null);  // object
@pk111and222

This comment has been minimized.

Copy link

@pk111and222 pk111and222 commented Oct 24, 2018

@C-somnus

This comment has been minimized.

Copy link

@C-somnus C-somnus commented Dec 3, 2018

那 Object.protototype.toString 究竟是一个什么样的方法呢?
单词错了~

@hncsjxxxx

This comment has been minimized.

Copy link

@hncsjxxxx hncsjxxxx commented Dec 23, 2018

image
看来我还是没懂原型链 。。。
Object.toString是将Object作为一个对象访问,Object自身没有属性toString,toString在Object的原型链上,Object.proto===Funtion.prototype,toString是Function.prototype的一个属性
image
MDN中说,Function 对象覆盖了从 Object 继承来的 Object.prototype.toString 方法。函数的 toString 方法会返回一个表示函数源代码的字符串。具体来说,包括 function关键字,形参列表,大括号,以及函数体中的内容。若 this 不是 Function 对象,则 toString() 方法将抛出 TypeError异常
image
不知道有没有对楼上的问题有一点帮助,如果有不对的地方,欢迎指出~

虽然我已经查过了,但是你的回答还是非常棒的。

大神,我还是看不懂,能不能请教一下你的理解

@rutingjin

This comment has been minimized.

Copy link

@rutingjin rutingjin commented Jan 15, 2019

   Object.toString === Object.__proto__.toString // true
   Object.__proto__.toString === Function.prototype.toString // true
   Object.toString === Function.prototype.toString // true
   Function.prototype.toString.call('')
   // Uncaught TypeError: Function.prototype.toString requires that 'this' be a Function
   Function.prototype.toString.call(function () { a = 1 }) === "function () { a = 1 }" // true
@zhoubhin

This comment has been minimized.

Copy link

@zhoubhin zhoubhin commented Feb 1, 2019

@hncsjxxxx
关于原型和原型链,可以查看这篇文章

@renaissanced

This comment has been minimized.

Copy link

@renaissanced renaissanced commented Feb 27, 2019

请问第一版中的 class2type[Object.prototype.toString.call(obj)] || "object" 后面这个"object"应该不需要吧,||前面的应该都包含所有情况了

@renaissanced

This comment has been minimized.

Copy link

@renaissanced renaissanced commented Feb 27, 2019

看到评论有人提这个了,知道啦

@DoubleMingZMM

This comment has been minimized.

Copy link

@DoubleMingZMM DoubleMingZMM commented Mar 12, 2019

实现type方法的时候为什么不直接用Object.prototype.toString.call()的形式判断所有类型,而是将基本数据类型使用typeof呢?是有什么问题吗?

@sclchic

This comment has been minimized.

Copy link

@sclchic sclchic commented Mar 14, 2019

@DoubleMingZMM 有一点是用typeof判断效率更高一点;
image

@rainjm

This comment has been minimized.

Copy link

@rainjm rainjm commented May 15, 2019

Object.prototype.toString.call(window)
"[object Window]"
Object.prototype.toString.call(document);
"[object HTMLDocument]"
@rainjm

This comment has been minimized.

Copy link

@rainjm rainjm commented Jun 24, 2019

typeof BigInt(1)
"bigint"

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

This comment has been minimized.

Copy link

@tan215210976 tan215210976 commented Aug 28, 2019

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

@KGKAI

This comment has been minimized.

Copy link

@KGKAI KGKAI commented Aug 29, 2019

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

@bowencool

This comment has been minimized.

Copy link

@bowencool bowencool commented Sep 1, 2019

请问下如何识别Proxy类型
image

@sunchengzhou

This comment has been minimized.

Copy link

@sunchengzhou sunchengzhou commented Nov 13, 2019

请问下如何识别Proxy类型
image

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

@yangtao2o

This comment has been minimized.

Copy link

@yangtao2o yangtao2o commented Dec 3, 2019

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

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 种了,乖乖~

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

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.