{"action":"created","issue":{"url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/28","repository_url":"https://api.github.com/repos/mqyqingfeng/Blog","labels_url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/28/labels{/name}","comments_url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/28/comments","events_url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/28/events","html_url":"https://github.com/mqyqingfeng/Blog/issues/28","id":238716046,"number":28,"title":"JavaScript专题之类型判断(上)","user":{"login":"mqyqingfeng","id":11458263,"avatar_url":"https://avatars3.githubusercontent.com/u/11458263?v=4","gravatar_id":"","url":"https://api.github.com/users/mqyqingfeng","html_url":"https://github.com/mqyqingfeng","followers_url":"https://api.github.com/users/mqyqingfeng/followers","following_url":"https://api.github.com/users/mqyqingfeng/following{/other_user}","gists_url":"https://api.github.com/users/mqyqingfeng/gists{/gist_id}","starred_url":"https://api.github.com/users/mqyqingfeng/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mqyqingfeng/subscriptions","organizations_url":"https://api.github.com/users/mqyqingfeng/orgs","repos_url":"https://api.github.com/users/mqyqingfeng/repos","events_url":"https://api.github.com/users/mqyqingfeng/events{/privacy}","received_events_url":"https://api.github.com/users/mqyqingfeng/received_events","type":"User","site_admin":false},"labels":[{"id":618327137,"url":"https://api.github.com/repos/mqyqingfeng/Blog/labels/%E4%B8%93%E9%A2%98%E7%B3%BB%E5%88%97","name":"专题系列","color":"0071b5","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":20,"created_at":"2017-06-27T01:51:48Z","updated_at":"2018-04-16T03:26:49Z","closed_at":null,"author_association":"OWNER","body":"## 前言\r\n\r\n类型判断在 web 开发中有非常广泛的应用，简单的有判断数字还是字符串，进阶一点的有判断数组还是对象，再进阶一点的有判断日期、正则、错误类型，再再进阶一点还有比如判断 plainObject、空对象、Window 对象等等。\r\n\r\n以上都会讲，今天是上半场。\r\n\r\n## typeof\r\n\r\n我们最最常用的莫过于 typeof，注意，尽管我们会看到诸如：\r\n\r\n```js\r\nconsole.log(typeof('yayu')) // string\r\n```\r\n\r\n的写法，但是 typeof 可是一个正宗的运算符，就跟加减乘除一样！这就能解释为什么下面这种写法也是可行的：\r\n\r\n```js\r\nconsole.log(typeof 'yayu') // string\r\n```\r\n\r\n引用《JavaScript权威指南》中对 typeof 的介绍：\r\n\r\n>typeof 是一元操作符，放在其单个操作数的前面，操作数可以是任意类型。返回值为表示操作数类型的一个字符串。\r\n\r\n那我们都知道，在 ES6 前，JavaScript 共六种数据类型，分别是：\r\n\r\nUndefined、Null、Boolean、Number、String、Object \r\n\r\n然而当我们使用 typeof 对这些数据类型的值进行操作的时候，返回的结果却不是一一对应，分别是：\r\n\r\nundefined、object、boolean、number、string、object \r\n\r\n注意以上都是小写的字符串。Null 和 Object 类型都返回了 object 字符串。\r\n\r\n尽管不能一一对应，但是 typeof 却能检测出函数类型：\r\n\r\n```js\r\nfunction a() {}\r\n\r\nconsole.log(typeof a); // function\r\n```\r\n\r\n所以 typeof 能检测出六种类型的值，但是，除此之外 Object 下还有很多细分的类型呐，如 Array、Function、Date、RegExp、Error 等。\r\n\r\n如果用 typeof 去检测这些类型，举个例子：\r\n\r\n```js\r\nvar date = new Date();\r\nvar error = new Error();\r\nconsole.log(typeof date); // object\r\nconsole.log(typeof error); // object\r\n```\r\n\r\n返回的都是 object 呐，这可怎么区分~ 所以有没有更好的方法呢？\r\n\r\n## Object.prototype.toString\r\n\r\n是的，当然有！这就是 Object.prototype.toString！\r\n\r\n那 Object.protototype.toString 究竟是一个什么样的方法呢？\r\n\r\n为了更加细致的讲解这个函数，让我先献上 ES5 规范地址：[https://es5.github.io/#x15.2.4.2](https://es5.github.io/#x15.2.4.2)。\r\n\r\n在第 15.2.4.2 节讲的就是 Object.prototype.toString()，为了不误导大家，我先奉上英文版：\r\n\r\n>When the toString method is called, the following steps are taken:\r\n\r\n>1. If the **this** value is **undefined**, return \"**[object Undefined]**\".\r\n>2. If the **this** value is **null**, return \"**[object Null]**\".\r\n>3. Let *O* be the result of calling ToObject passing the **this** value as the argument.\r\n>4. Let *class* be the value of the [[Class]] internal property of *O*.\r\n>5. Return the String value that is the result of concatenating the three Strings \"**[object** \", *class*, and \"**]**\".\r\n\r\n凡是规范上加粗或者斜体的，在这里我也加粗或者斜体了，就是要让大家感受原汁原味的规范！\r\n\r\n如果没有看懂，就不妨看看我理解的：\r\n\r\n当 toString 方法被调用的时候，下面的步骤会被执行：\r\n\r\n1. 如果 this 值是 undefined，就返回 [object Undefined]\r\n2. 如果 this 的值是 null，就返回 [object Null]\r\n3. 让 O 成为 ToObject(this) 的结果\r\n4. 让 class 成为 O 的内部属性 [[Class]] 的值\r\n5. 最后返回由 \"[object \" 和 class 和 \"]\" 三个部分组成的字符串\r\n\r\n通过规范，我们至少知道了调用 Object.prototype.toString 会返回一个由 \"[object \" 和 class 和 \"]\" 组成的字符串，而 class 是要判断的对象的内部属性。\r\n\r\n让我们写个 demo:\r\n\r\n```js\r\nconsole.log(Object.prototype.toString.call(undefined)) // [object Undefined]\r\nconsole.log(Object.prototype.toString.call(null)) // [object Null]\r\n\r\nvar date = new Date();\r\nconsole.log(Object.prototype.toString.call(date)) // [object Date]\r\n```\r\n\r\n由此我们可以看到这个 class 值就是识别对象类型的关键！\r\n\r\n正是因为这种特性，我们可以用 Object.prototype.toString 方法识别出更多类型！\r\n\r\n那到底能识别多少种类型呢？\r\n\r\n至少 12 种！\r\n\r\n你咋知道的？\r\n\r\n我数的！\r\n\r\n……\r\n\r\n让我们看个 demo:\r\n\r\n```js\r\n// 以下是11种：\r\nvar number = 1;          // [object Number]\r\nvar string = '123';      // [object String]\r\nvar boolean = true;      // [object Boolean]\r\nvar und = undefined;     // [object Undefined]\r\nvar nul = null;          // [object Null]\r\nvar obj = {a: 1}         // [object Object]\r\nvar array = [1, 2, 3];   // [object Array]\r\nvar date = new Date();   // [object Date]\r\nvar error = new Error(); // [object Error]\r\nvar reg = /a/g;          // [object RegExp]\r\nvar func = function a(){}; // [object Function]\r\n\r\nfunction checkType() {\r\n    for (var i = 0; i < arguments.length; i++) {\r\n        console.log(Object.prototype.toString.call(arguments[i]))\r\n    }\r\n}\r\n\r\ncheckType(number, string, boolean, und, nul, obj, array, date, error, reg, func)\r\n\r\n```\r\n\r\n除了以上 11 种之外，还有：\r\n\r\n```js\r\nconsole.log(Object.prototype.toString.call(Math)); // [object Math]\r\nconsole.log(Object.prototype.toString.call(JSON)); // [object JSON]\r\n```\r\n\r\n除了以上 13 种之外，还有：\r\n\r\n```js\r\nfunction a() {\r\n    console.log(Object.prototype.toString.call(arguments)); // [object Arguments]\r\n}\r\na();\r\n```\r\n\r\n所以我们可以识别至少 14 种类型，当然我们也可以算出来，[[class]] 属性至少有 12 个。\r\n\r\n## type API\r\n\r\n既然有了 Object.prototype.toString 这个神器！那就让我们写个 type 函数帮助我们以后识别各种类型的值吧！\r\n\r\n我的设想：\r\n\r\n写一个 type 函数能检测各种类型的值，如果是基本类型，就使用 typeof，引用类型就使用 toString。此外鉴于 typeof 的结果是小写，我也希望所有的结果都是小写。\r\n\r\n考虑到实际情况下并不会检测 Math 和 JSON，所以去掉这两个类型的检测。\r\n\r\n我们来写一版代码：\r\n\r\n```js\r\n// 第一版\r\nvar class2type = {};\r\n\r\n// 生成class2type映射\r\n\"Boolean Number String Function Array Date RegExp Object Error Null Undefined\".split(\" \").map(function(item, index) {\r\n    class2type[\"[object \" + item + \"]\"] = item.toLowerCase();\r\n})\r\n\r\nfunction type(obj) {\r\n    return typeof obj === \"object\" || typeof obj === \"function\" ?\r\n        class2type[Object.prototype.toString.call(obj)] || \"object\" :\r\n        typeof obj;\r\n}\r\n```\r\n\r\n嗯，看起来很完美的样子~~ 但是注意，在 IE6 中，null 和 undefined 会被 Object.prototype.toString 识别成 [object Object]！\r\n\r\n我去，竟然还有这个兼容性！有什么简单的方法可以解决吗？那我们再改写一版，绝对让你惊艳！\r\n\r\n```js\r\n// 第二版\r\nvar class2type = {};\r\n\r\n// 生成class2type映射\r\n\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \").map(function(item, index) {\r\n    class2type[\"[object \" + item + \"]\"] = item.toLowerCase();\r\n})\r\n\r\nfunction type(obj) {\r\n    // 一箭双雕\r\n    if (obj == null) {\r\n        return obj + \"\";\r\n    }\r\n    return typeof obj === \"object\" || typeof obj === \"function\" ?\r\n        class2type[Object.prototype.toString.call(obj)] || \"object\" :\r\n        typeof obj;\r\n}\r\n```\r\n\r\n## isFunction\r\n\r\n有了 type 函数后，我们可以对常用的判断直接封装，比如 isFunction:\r\n\r\n```js\r\nfunction isFunction(obj) {\r\n    return type(obj) === \"function\";\r\n}\r\n```\r\n\r\n## 数组\r\n\r\njQuery 判断数组类型，旧版本是通过判断 Array.isArray 方法是否存在，如果存在就使用该方法，不存在就使用 type 函数。\r\n\r\n```js\r\nvar isArray = Array.isArray || function( obj ) {\r\n    return type(obj) === \"array\";\r\n}\r\n```\r\n\r\n但是在 jQuery v3.0 中已经完全采用了 Array.isArray。\r\n\r\n## 结语\r\n\r\n到此，类型判断的上篇就结束了，我们已经可以判断日期、正则、错误类型啦，但是还有更复杂的判断比如 plainObject、空对象、Window对象、类数组对象等，路漫漫其修远兮，吾将上下而求索。\r\n\r\n哦， 对了，这个 type 函数抄的 jQuery，[点击查看 type 源码](https://github.com/jquery/jquery/blob/ac9e3016645078e1e42120822cfb2076151c8cbe/src/core.js#L269)。\r\n\r\n## 专题系列\r\n\r\nJavaScript专题系列目录地址：[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)。\r\n\r\nJavaScript专题系列预计写二十篇左右，主要研究日常开发中一些功能点的实现，比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等，特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。\r\n\r\n如果有错误或者不严谨的地方，请务必给予指正，十分感谢。如果喜欢或者有所启发，欢迎 star，对作者也是一种鼓励。\r\n"},"comment":{"url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/comments/381470028","html_url":"https://github.com/mqyqingfeng/Blog/issues/28#issuecomment-381470028","issue_url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/28","id":381470028,"user":{"login":"Tan90Qian","id":20704473,"avatar_url":"https://avatars3.githubusercontent.com/u/20704473?v=4","gravatar_id":"","url":"https://api.github.com/users/Tan90Qian","html_url":"https://github.com/Tan90Qian","followers_url":"https://api.github.com/users/Tan90Qian/followers","following_url":"https://api.github.com/users/Tan90Qian/following{/other_user}","gists_url":"https://api.github.com/users/Tan90Qian/gists{/gist_id}","starred_url":"https://api.github.com/users/Tan90Qian/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tan90Qian/subscriptions","organizations_url":"https://api.github.com/users/Tan90Qian/orgs","repos_url":"https://api.github.com/users/Tan90Qian/repos","events_url":"https://api.github.com/users/Tan90Qian/events{/privacy}","received_events_url":"https://api.github.com/users/Tan90Qian/received_events","type":"User","site_admin":false},"created_at":"2018-04-16T03:26:49Z","updated_at":"2018-04-16T03:26:49Z","author_association":"NONE","body":"@mqyqingfeng 看到了，之前邮箱一直没有邮件提醒，我还以为dalao最近没空。从月初开始，每天看2、3篇博客，顺便“找茬”，收获非常大～"}}
{"actor":{"display_login":"Tan90Qian"}}