js中数据类型判断

在 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 nulltypeof valtypeof (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
上一篇:An error occurred uploading to the iTunes Store - Please upgrade Java


下一篇:HDFS Users Guide--官方文档