在 ECMAScript 规范中,共定义了 7 种数据类型
基础数据类型有6种: Null、Undefined、Number、 String、 Boolean 和 Symbol (es6 引入)
引用数据类型有1种:Object ,其又可细分为Array、Map、Set 等
那么,对于所给数据,我们该如何准确获取其数据类型呢?本文将就这一问题进行详细阐述。
前置代码
const nul = null
const undef = undefined
const num = 1
const str = 'test'
const bool = true
const symbol = Symbol()
const obj = {}
const arr = []
const func = () => {}
1. typeof
typeof 是一个操作符,其右侧为待判断类型的数据,可以是一个值、一个变量或一个表达式,如
typeof null
、typeof val
、typeof (a === b)
等,其中 val、a 和 b 为变量名
1.1 示例
console.log(typeof nul) // object
console.log(typeof undef) // undefined
console.log(typeof num) // number
console.log(typeof str) // string
console.log(typeof bool) // boolean
console.log(typeof symbol) // symbol
console.log(typeof obj) // object
console.log(typeof arr) // object
console.log(typeof func) // function
-
对于基本类型,除 null 以外,均可以返回正确的结果。
-
对于引用类型,除 function 以外,一律返回 object 类型。
-
对于 null ,返回 object 类型。
-
对于 function 返回 function 类型。
1.2 缺陷
从上面的代码可以看到,使用typeof判断null的数据类型时, 得到的结果为object, 为什么会这样呢?
其实这是一个历史遗留问题,在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回 "object"
。
使用typeof判断数据类型缺陷如下:
(1) 无法正确判断null值的数据类型。
(2) 无法获取引用数据类型的详细类型值。
2. instanceof
instanceof是一个操作符,用来判断对象的具体类,其左侧为待判断类型的对象, 右侧为一个类
2.1 示例
console.log(obj instanceof Object) // true
console.log(arr instanceof Array) // true
console.log(func instanceof Function) // true
2.2 原理
首先,我们需要简单了解一下原型链,数据的__proto__属性指向其原型,其原型的__proto__属性指向上一层原型。对于原型的获取与修改,不建议直接用__proto__直接操作,可以使用Reflect.getPrototypeOf()、Reflect.setPrototypeOf()
或Object.getPrototypeOf()、Object.setPrototypeOf()。
console.log(arr.__proto__) // Object(0) []
console.log(Object.getPrototypeOf(arr)) // Object(0) []
console.log(Reflect.getPrototypeOf(arr)) // Object(0) []
实际上,instanceof 就是利用原型链来判断数据类型的,我们不妨来看看以下代码
class A {}
class B extends A {}
const b = new B()
console.log(b instanceof B) // true
console.log(b instanceof A) // true
console.log(b instanceof Object) // true
console.log(B.prototype) // A {}
console.log(A.prototype) // {}
console.log(Object.prototype) // [Object: null prototype] {}
console.log(getProto(b)) // [ A {}, {}, [Object: null prototype] {} ]
// 获取原型链
function getProto (obj) {
const protoArr = []
let proto
while (proto = Object.getPrototypeOf(obj)) {
protoArr.push(proto)
obj = proto
}
return protoArr
}
不难发现,当左侧对象为b,右侧类为B、A或Object时,表达式为true,同时我们也发现,B、A和Object所指向的原型,都在对象b的原型链上,由此不难得出,instanceof 是将右侧类所指向的原型与左侧对象原型链上的原型进行一一比较,当该原型存在于原型链上时,返回true。
在清晰instanceof的工作原理后,我们不妨手动实现一个instanceOf函数
console.log(instanceOf(b, B)) // true
console.log(instanceOf(b, A)) // true
console.log(instanceOf(b, Object)) // true
// 模拟实现instanceof
function instanceOf (obj, Class) {
const protoArr = getProto(obj)
return protoArr.some(it => it === Class.prototype)
}
2.3 缺陷
(1) 无法判断基础数据类型的类型值。
(2) 如果原型链发生改变,会导致判断结果不准确,代码举例如下
class B {}
const b = new B()
Object.setPrototypeOf(b, null)
console.log(b instanceof B) // false
(3) 通过Symbol.hasInstance自定义instanceof在类上的行为,会导致判断结果不准确,代码举例如下
class MyArray {
static [Symbol.hasInstance](instance) {
return true;
}
}
console.log({ a: 1 } instanceof MyArray); // true
3. constructor
每个对象都有其构造函数,通过构造函数,我们可以判断其数据类型
3.1 示例
null 和 undefined 是无效的对象,不存在constructor,不能用constructor判断数据类型
console.log(num.constructor) // [Function: Number]
console.log(str.constructor) // [Function: String]
console.log(bool.constructor) // [Function: Boolean]
console.log(symbol.constructor) // [Function: Symbol]
console.log(obj.constructor) // [Function: Object]
console.log(arr.constructor) // [Function: Array]
console.log(func.constructor) // [Function: Function]
console.log(num.constructor === Number) // true
console.log(str.constructor === String) // true
console.log(bool.constructor === Boolean) // true
console.log(symbol.constructor === Symbol) // true
console.log(obj.constructor === Object) // true
console.log(arr.constructor === Array) // true
console.log(func.constructor === Function) // true
3.2 缺陷
(1) 无法判断null、undefined的数据类型。
(2) 对象的 constructor 是不稳定的,其依赖原型的constructor属性,当开发者修改原型后,可能会导致判断结果错误,如
function F () {}
function D () {}
const f = new F()
console.log(f.constructor) // [Function: F]
// 修改原型属性
F.prototype.constructor = D
console.log(f.constructor) // [Function: D]
4. toString()
4.1 示例
我们可以使用Object中的toString()方法来获取数据类型,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型具体用法如下:
Object.prototype.toString.call(data)
, data为待判断类型的数据
console.log(Object.prototype.toString.call(nul)) // [object Null]
console.log(Object.prototype.toString.call(undef)) // [object Undefined]
console.log(Object.prototype.toString.call(num)) // [object Number]
console.log(Object.prototype.toString.call(str)) // [object String]
console.log(Object.prototype.toString.call(bool)) // [object Boolean]
console.log(Object.prototype.toString.call(symbol)) // [object Symbol]
console.log(Object.prototype.toString.call(obj)) // [object Object]
console.log(Object.prototype.toString.call(arr)) // [object Array]
console.log(Object.prototype.toString.call(func)) // [object Function]
4.2 缺陷
无法精准判断自定义类对象的数据类型
class E {}
const e = new E()
console.log(Object.prototype.toString.call(e)) // [object Object]
5. 总结
方式名称 | 缺陷 |
---|---|
typeof | (1) null值判断错误 (2) 引用数据类型无法精准判断 |
instanceof | (1) 无法判断基础数据类型 (2) 修改原型可能导致判断错误 (3) 自定义行为可能导致判断错误 |
constructor | (1) null、undefined无法判断 (2) 修改原型可能导致判断错误 |
toString() | 无法精准判断自定义类对象的数据类型 |
由表可知,不同情况下我们可以使用不同的判断方式
- 判断基础数据类型:toString()
- 判断非自定义引用数据类型:toString()、 instanceof 或 constructor
- 判断自定义引用数据类型: instanceof 或 constructor