blog icon indicating copy to clipboard operation
blog copied to clipboard

JavaScript类型判断的1010种方式(史上最全)

Open campcc opened this issue 6 years ago • 0 comments

在最新的 ECMAScript规范 中,一共定义了 7 种数据类型,

  • 基本类型:String、Number、Boolean、Symbol、Undefined、Null
  • 引用类型:Object

基本类型是存储在 栈(Stack) 中的简单数据段,引用类型的值是存储在 堆(Heap) 中的对象。

数据类型的值存储在堆中还是栈中,取决于值的特性。基本类型占据的空间固定,存储在栈中按值访问,可以提升变量的查询速度。引用类型的值大小不固定,不适合存储在栈中,JavaScript中采取的做法是,将引用类型的值存储在堆中,同时在栈中存储值的访问地址,所以引用类型是 按地址访问 的。

var arr = [1, 2, 3] // 引用类型的值其实就是存储在堆内存中的对象,访问方式为按地址访问,首先找到栈区中值的地址,然后沿着地址找到堆内存中对应的对象。

对于数据类型的判断,JavaScript 也提供了很多种方法,遗憾的是,不同的方法得到的结果参差不齐,下面列出了我所知道的所有的类型判断方法,如果有遗漏,欢迎在评论区补充。

typeof

typeof 返回未经计算的操作数的类型的字符串,可能会有一些意料之外的结果:

typeof null  === 'object' // true, 从一开始出现 JavaScript 就是这样的
typeof NaN === 'number' // true, 尽管 NaN 是 Not-A-Number 的缩写,typeof 还是会返回 number
typeof [] === 'object' // true, 引用类型除了 function 外,都返回 object
typeof Infinity  === 'number' // true
typeof function() {} === 'function' // true

typeof 能够判断大多数类型,总结 typeof 的运算规则就是:

  • 对于基本类型,除了 null 以外都可以返回正确结果
  • 对于引用类型,除了 function 以外都返回 object
  • 对于 null,返回 object
  • 对于函数类型,返回 function

instanceof

instanceof 用来判断 A 是否是 B 的实例,所以 instanceof 可以正确地判断对象的类型,因为其内部的实现机制是通过判断对象的原型链中是否能找到类型的 prototype

[] instanceof Array // true
[].__proto__ === Array.prototype // true

也正是因为 instanceof 的实现本质上是基于原型链的查找,所以也会出现意料之外的情况:

[] instanceof Object // true
[].__proto__.__proto__ === Object.prototype // true

此外,instanceof 操作符的语法是 object instanceof constructor,要求操作符的左右两侧都必须是对象,所以无法判断 null, undefined 等类型。

constructor

我们知道当创建一个函数 F 时,JavaScript 引擎会为 F 添加 prototype 属性,然后在 prototype 属性上添加一个 constructor 属性,让它指向 F 的引用:

function F() {}
F.prototype.constructor === F // true

这是引擎默认的行为,目的是表明对象是由那个函数构造的,在 JavaScript 中,function 其实就是一个语法糖,所有的函数本质上都是一个 Function 对象。利用这一特性,我们可以通过 constructor 来判断对象的数据类型,因为 JavaScript 为我们提供了很多的内置对象:

[].constructor === Array // true
''.constructor === String // true
false.constructor === Boolean // true
new Number().constructor === Number // true

但是 constructor判断类型是 “不可靠的”,因为 constructor 属性可以被修改,比如:

var a = 1
a.constructor === Number // true
a.__proto__.constructor = String
a.constructor === Number // false
a.constructor === String // true

constructor 也不能用来判断 nullundefined,因为他们都不是对象。

Object.prototype.toString.call

Object 原型上的方法 toString 返回一个表示该对象的字符串 [object Xxx]

ECMAScript 5 和随后的 Errata 中定义,从 JavaScript 1.8.5 开始,toString() 调用 null 返回 [object Null]undefined 返回 [object Undefined]。对于 Object 对象,直接调用 toString 就可以获得类型字符串,其他类型,需要借助 Object.prototype.toString.call/apply 来返回正确的类型值。至此,toString 成为判断数据类型最完备的一种方法:

Object.prototype.toString.call('') // [object String]
Object.prototype.toString.call(/([A-Z])\w+/g) // [object RegExp]
Object.prototype.toString.call(null) // [object Null]
Object.prototype.toString.call(undefined) // [object Undefined]
Object.prototype.toString.call(NaN) // [object Number]

我们只需要对 toString 稍加封装就可以实现 JavaScript 中所有类型的判断,举个例子:

function getType(obj) {
  const typeClass = Object.prototype.toString.call(obj)
  const classMatch = {
    '[object String]': 'string',
    '[object Number]': 'number',
    '[object Boolean]': 'boolean',
    '[object Symbol]': 'symbol',
    '[object Object]': 'object',
    '[object Array]': 'array',
    '[object Function]': 'function',
    '[object RegExp]': 'regexp',
    '[object Date]': 'date',
    '[object Error]': 'error',
    '[object Window]': 'window',
    '[object HTMLDocument]': 'document'
  }
  if (obj == null) { // 如果是 null 或 undefined ,调用 toString 后返回
    return obj + ''
  } else { // 如果是 object 和 function ,调用 Object.prototype.toString 匹配具体的类型,其余情况直接调用 typeof
    return (typeof obj === 'object' || typeof obj === 'function') ? classMatch[typeClass] : typeof obj
  }
}

勘误与提问

如果有疑问或者发现错误,可以在相应的 issues 进行提问或勘误

如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励

(完)

campcc avatar Aug 16 '19 07:08 campcc