1. typeof
typeof 是 Javascript 的一个操作符,可以表示未经计算的操作数的类型。
如下是摘录自 MDN 的 typeof
可能输出的值的列表:
类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
BigInt | "bigint" |
String | "string" |
Symbol (ECMAScript 2015 新增) | "symbol" |
宿主对象(由 JS 环境提供) | 取决于具体实现 |
Function 对象 (按照 ECMA-262 规范实现 [[Call]]) | "function" |
其他任何对象 | "object" |
使用 typeof
时要特别注意以下两个非预想的结果:
typeof null
输出为 “object”typeof NaN
输出为 “number” (NaN:Not-A-Number)
此外 MDN 附加了以下 typeof
的诡异特性,在此进行摘录:
null
1 | // JavaScript 诞生以来便如此 |
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回 "object"
。(参考来源)
曾有一个 ECMAScript 的修复提案(通过选择性加入的方式),但被拒绝了。该提案会导致 typeof null === 'null'
。
使用 new
操作符
1 | // 除 Function 外的所有构造函数的类型都是 'object' |
语法中的括号
1 | // 括号有无将决定表达式的类型。 |
正则表达式
对正则表达式字面量的类型判断在某些浏览器中不符合标准:
1 | typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1 |
错误
在 ECMAScript 2015 之前,typeof
总能保证对任何所给的操作数返回一个字符串。即便是没有声明的标识符,typeof
也能返回 'undefined'
。使用 typeof
永远不会抛出错误。
但在加入了块级作用域的 let 和 const 之后,在其被声明之前对块中的 let
和 const
变量使用 typeof
会抛出一个 ReferenceError。块作用域变量在块的头部处于“暂存死区”,直至其被初始化,在这期间,访问变量将会引发错误。
1 | typeof undeclaredVariable === 'undefined'; |
例外
当前所有的浏览器都暴露了一个类型为 undefined
的非标准宿主对象 document.all
。
1 | typeof document.all === 'undefined'; |
尽管规范允许为非标准的外来对象自定义类型标签,但它要求这些类型标签与已有的不同。document.all
的类型标签为 'undefined'
的例子在 Web 领域中被归类为对原 ECMA JavaScript 标准的“故意侵犯”。
2. instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,换句话说,instanceof 可以帮助我们来判断一个对象是否是否继承与另一个对象。
内部原理
其实这里可以看一下 instanceof
运算符代码:
1 | function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 |
可以很容易发现,instance_of
方法取出了右边对象 R 的 prototype
属性,然后使用了 while
循环一层一层的去调出左边对象 L 的 __proto__
隐式原型,按照原型链的调用规则,如果 L 继承与 R ,那 L 在某一层的隐式原型一定与 R 的显示原型完全相等。
举个例子:
1 | console.log(Number instanceof Number);//false |
上面第一组的解析我们很容易就能明白,因为一个对象本身的隐式原型与其显示原型不相等,那么肯定返回 false
;第二组,Foo 函数构造于 Function
这是一个很标准的原型继承;而第三组似乎有些特殊,但是仔细看一下原型继承图我们就很容易看明白,对于 Object 来说 Object.__proto__.proto__ === Object.prototype
,对于 Function 来说 Function.__proto__ === Function.prototype
。
特殊情况
对于字面量而非对象的元素,不可以使用 instanceof 来判断类型,因为其本身并非是 Object,因此会出现如下的情况:
1 | let num = 123 |
1 | let num = new Number(123) |
但有趣的一点是,我们直接对比变量的隐式原型,其指向的正是这些内置对象的显式原型:
1 | let str = "string" |
按照 instance 的判断规则,可以判断出 str instanceof String
返回结果是 true
,因此说明其底层对字面量元素进行了屏蔽,使其直接返回了 false 。因此对于字面量的 Number 或者 String,只能使用 typeof 来判断或者 Object.prototype.toString()
来判断。
3.Object.prototype.toString()
被改写的 toString()
由于 Object 对象的原型上挂载了一个 toString()
方法,因此根据原型链的调用规则,在 Javascript 中每个对象都可以调用 toString()
方法,其本身原意为返回一个表示该对象的字符串。
我们可以通过创建一个对象来调用该方法:
1 | let obj = new Object() |
但是当我们调用 Number 类型或者 Array 类型时,其结果为:
1 | let num = 123 |
这是由于在 Number.prototype
与 Array.prototype
上已经改写了 toString()
方法,123.toString()
执行的其实是 Number.prototype.toString()
而并非 Object.prototype.toString()
。对于大部分的 Javascript 内建类型来说,都改写了 toString()
方法,用户自行创建的构造函数也可以通过在 prototype
上挂载 toString()
方法达到改写的目的。以下的表格列举了常见的类型对象调用 toString()
方法所输出的结果:
数据类型 | 例子 | return |
---|---|---|
字符串 | “foo”.toString() | “foo” |
数字 | 1.toString() | Uncaught SyntaxError: Invalid or unexpected token |
布尔值 | false.toString() | “false” |
undefined | undefined.toString() | Uncaught TypeError: Cannot read property ‘toString’ of undefined |
null | null.toString() | Uncaught TypeError: Cannot read property ‘toString’ of null |
String | String.toString() | “function String() { [native code] }” |
Number | Number.toString() | “function Number() { [native code] }” |
Boolean | Boolean.toString() | “function Boolean() { [native code] }” |
Array | Array.toString() | “function Array() { [native code] }” |
Function | Function.toString() | “function Function() { [native code] }” |
Date | Date.toString() | “function Date() { [native code] }” |
RegExp | RegExp.toString() | “function RegExp() { [native code] }” |
Error | Error.toString() | “function Error() { [native code] }” |
Promise | Promise.toString() | “function Promise() { [native code] }” |
Obejct | Object.toString() | “function Object() { [native code] }” |
Math | Math.toString() | “[object Math]“ |
那如果我们想强制让某一对象调用 Object.prototype.toString()
方法会发生什么呢?我们使用 call
来改写方法中的 this
可以达到这一效果:
1 | let num = 123 |
可以发现通过借助 Object.prototype.toString()
我们可以获取到调用对象的类型,这一点非常有用,可以帮助我们接下来进行类型判断。
内部原理
在编写类型判断的方法之前,我们不妨来看一下 Object.prototype.toString
到底做了什么,在不同的 ES 版本中,该方法会有一定的区别:
ES5 环境下:
- 如果this的值为undefined,则返回
"[object Undefined]"
. - 如果this的值为null,则返回
"[object Null]"
. - 让O成为调用ToObject(this)的结果.
- 让class成为O的内部属性[[Class]]的值.
- 返回三个字符串“[object “, class, 以及 “]“连接后的新字符串.
ES6 环境下:
- 如果this的值为undefined,则返回
"[object Undefined]"
. - 如果this的值为null,则返回
"[object Null]"
. - 让O成为调用ToObject(this)的结果.
- 如果O有[[NativeBrand]]内部属性,让tag成为表29中对应的值.
- 否则
- 让hasTag成为调用O的[[HasProperty]]内部方法后的结果,参数为@@toStringTag.
- 如果hasTag为false,则让tag为
"Object"
. - 否则,
- 让tag成为调用O的[[Get]]内部方法后的结果,参数为@@toStringTag.
- 如果tag是一个abrupt completion,则让tag成为NormalCompletion(
"???"
). - 让tag成为tag.[[value]].
- 如果Type(tag)不是字符串,则让tag成为
"???"
. - 如果tag的值为
"Arguments"
,"Array"
,"Boolean"
,"Date"
,"Error"
,"Function"
,"JSON"
,"Math"
,"Number"
,"Object"
,"RegExp"
,或者"String"中的任一个,则让
tag成为字符串"~"和
tag当前的值连接后的结果.
- 返回三个字符串”[object “, tag, and “]“连接后的新字符串.
封装一个类型判断的方法
1 | function type (data){ |