三、JavaScript专题之类型判断(上)
在项目开发中,我们经常会进行类型判断,这里对JS类型判断的方式进行一下总结。
在MDN中根据最新的 ECMAScript 标准定义了 8 种数据类型:
基本类型(7种):String, Number, Boolean, null, undefined, Bigint, Symbol。
引用类型(1种):Object
下面我们来对这些类型进行检测。
typeof
语法
typeof 操作符的唯一目的就是检查数据类型,但是如果我们希望检查任何从 Object 派生出来的结构类型,使用 typeof 是不起作用的,因为总是会得到 “object”。
| 类型 | 结果 |
|---|---|
| undefined | “undefined” |
| Boolean | “boolean” |
| Number | “number” |
| String | “string” |
| BigInt | “bigint” |
| Symbol | “symbol” |
| null | “object” |
| Function | “function” |
| 其他任何对象 | “object” |
注意:
- typeof Function: 结果是”function”,因为Function是一个构造函数,可以通过new操作符来构造对象
- typeof null:结果是”object”,null是一个只有一个值的特殊类型。表示一个空对象引用。
- typeof无法区分各种内置的对象,如Array, Date等。
综上,typeof操作符能能返回的类型:
- string number boolean undefined symbol bigint
- object 、(typeof null === ‘object’)
- function
原理
JS是动态类型的变量,每个变量在存储时除了存储变量值外,还需要存储变量的类型。JS里使用32位(bit)存储变量信息。低位的1~3个bit存储变量类型信息,叫做类型标签(type tag)
1 | .... XXXX X000 // object |
- 只有int类型的type tag使用1个bit,并且取值为1,其他都是3个bit, 并且低位为0。这样可以通过type tag低位取值判断是否为int数据;
- 为了区分int,还剩下2个bit,相当于使用2个bit区分这四个类型:object, double, string, boolean;
- 但是null,undefined和Function并没有分配type tag。
如何识别Function
函数并没有单独的type tag,因为函数也是对象。typeof内部判断如果一个对象实现了[[call]]内部方法则认为是函数。
如何识别undefined
undefined变量存储的是个特殊值JSVAL_VOID(0-2^30),typeof内部判断如果一个变量存储的是这个特殊值,则认为是undefined。
1 | #define JSVAL_VOID INT_TO_JSVAL(0 - JSVAL_INT_POW2(30)) |
如何识别null
null变量存储的也是个特殊值JSVAL_NULL,并且恰巧取值是空指针机器码(0),正好低位bit的值跟对象的type tag是一样的,这也是 typeof null === ‘object’ 的原因。
Object.prototype.toString
语法
前面已经知道typeof Object 只会返回’object’,无法区分 Object 下细分的类型呐,如 Array、Function、Date、RegExp、Error 等。
所以我们一般使用Object.prototype.toString区分各种内置对象。
1 | console.log(Object.prototype.toString.call(1)); // [object Number],隐式类型转换 |
- 如果实参是个基本类型,会自动转成对应的引用类型;
Object.prototype.toString不能区分基本类型的,只是用于区分各种对象;
- null和undefined不存在对应的引用类型,内部特殊处理了;
原理
每个对象都有个内部属性[[Class]],内置对象的[[Class]]的值都是不同的(”Arguments”, “Array”, “Boolean”, “Date”, “Error”, “Function”, “JSON”, “Math”, “Number”, “Object”, “RegExp”, “String”),并且目前[[Class]]属性值只能通过Object.prototype.toString访问。
Symbol.toStringTag属性
其实Object.prototype.toString内部先访问对象的Symbol.toStringTag属性值拼接返回值的。
1 | var a = "hello" |
所以不要轻易修改内置对象的Symbol.toStringTag属性值,否则Object.prototype.toString就不能正常使用。
Object.prototype.toString内部逻辑
在ECMA-262规范中,第19.1.3.6 Object.prototype.toString ( )条介绍:
When the toString toString method is called, the following steps are taken:
- If the this value is undefined, return “[object Undefined]”.
- If the this value is null, return “[object Null]”.
- Let O be ToObject(this value).
- Let isArray be IsArray(O).
- ReturnIfAbrupt(isArray).
- If isArray is true, let builtinTag be “Array”.
- Else, if O is an exotic String object, let builtinTag be “String”.
- Else, if O has an [[ParameterMap]] internal slot, let builtinTag be “Arguments”.
- Else, if O has a [[Call]] internal method, let builtinTag be “Function”.
- Else, if O has an [[ErrorData]] internal slot, let builtinTag be “Error”.
- Else, if O has a [[BooleanData]] internal slot, let builtinTag be “Boolean”.
- Else, if O has a [[NumberData]] internal slot, let builtinTag be “Number”.
- Else, if O has a [[DateValue]] internal slot, let builtinTag be “Date”.
- Else, if O has a [[RegExpMatcher]] internal slot, let builtinTag be “RegExp”.
- Else, let builtinTag be “Object”.
- Let tag be Get (O, @@toStringTag).
- ReturnIfAbrupt(tag).
- If Type(tag) is not String, let tag be builtinTag.
- Return the String that is the result of concatenating “[object “, tag, and “]”.
Instanceof
语法
1 | object instanceof constructorFunc |
instanceof 操作符判断构造函数constructorFunc的prototype属性是否在对象object的原型链上。
1 | Object.create({}) instanceof Object // true |
- 作为类型判断的一种方式,instanceof 操作符不会对变量object进行隐式类型转换
1
2"" instanceof String; // false,基本类型不会转成对象
new String('') instanceof String; // true - 对于没有原型的对象或则基本类型直接返回false
1
21 instanceof Object // false
Object.create(null) instanceof Object // false - constructorFunc必须是个对象。并且大部分情况要求是个构造函数(即要具有prototype属性)
1
2
3
4
5
6
7
8// TypeError: Right-hand side of 'instanceof' is not an object
1 instanceof 1
// TypeError: Right-hand side of 'instanceof' is not callable
1 instanceof ({})
// TypeError: Function has non-object prototype 'undefined' in instanceof check
({}) instanceof (() => {})
原理
参照ECMA:
1 | 1. 如果constructorFunc不是个对象,或则是null,直接抛TypeError异常; |
内置的类型判断方法
Array.isArray
ES5引入了方法Array.isArray专门用于数组类型判断。Object.prototype.toString和instanceof都不够严格
1 | var arr = [] |
不过现实情况下基本都是利用Object.prototype.toString作为Array.isArray的polyfill:
1 | if (!Array.isArray) { |