JavaScript 发展至今,一直保持着弱类型(weakly typed)语言的特性,也就一直没有提供准确的检测数据类型的内置方法。因此开发者就不得不自己编写校验数据类型的函数方法。本文就来介绍一下,如何准确的判断 JavaScript 中的数据类型。
根据 MDN 的《JavaScript data types and data structures》这篇文档介绍,JavaScript 将数据分为了以下几个大类:
Primitive values(原始值)
其中原始值是是我们最常用的“数据”,JavaScript 有7个原始值:
利用 typeof 关键字判断原始值
原始值基本都是可以使用 JavaScript 中提供的 typeof 关键字来判断数据类型的:
let value
typeof value // -> 'undefined'
typeof false // -> 'boolean'
typeof 23 // -> 'number'
typeof BigInt(66) // -> 'bigint'
typeof 'JavaScript' // -> 'string'
typeof Symbol('prop') // -> 'symbol'
前面之所以说原始值基本都是可以用 typeof 来判断数据类型,是因为 null 是一个例外:
typeof null // -> 'object'
null 是一个对象,是不是觉得很奇怪?
typeof 关键字对判断对象(引用类型)的值
除原始值以外,JavaScript 中其它的值都应该是对象(引用)类型的值。typeof 关键对其它的值的判断几乎就没有什么做了,例如以下判断:
typeof [] // -> 'object'
typeof {} // -> 'object'
typeof function(){} // -> 'object'
typeof class {} // -> 'object'
typeof new Date() // -> 'object'
typeof new RegExp() // -> 'object'
typeof new Error() // -> 'object'
利用 typeof 判断对象类型的值,结果都一样:’object’(对象)。而作为开发者是希望知道这些“对象”具体是什么类型的“对象”。那么如何获取对象的具体类型呢?
对象(Object)类型数据的细分
如何获取对象的具体类型呢?要找到这个问题的解决方案,我们就必须知道对象(Object)类型的数据到底有哪些更细的类别分的:
完整的所有 JavaScript 内置对象请参考 Standard built-in objects。
Indexed collections
- Array
- Int8Array
- Uint8Array
- Uint8ClampedArray
- Int16Array
- Uint16Array
- Int32Array
- Uint32Array
- BigInt64Array
- BigUint64Array
- Float32Array
- Float64Array
Keyed collections
Structured data
Fundamental objects
Dates
Error objects
对象(Object)类型数据的判断
在了解对象的类型后,现在就需要想办法来判断这些具体的对象数据类型了。
Object.prototype.toString()
利用 typeof 判断对象类型的数据,几乎是无效的,好在我们可以使用 Object.prototype.toString() 来获取对象的具体类型的字符:
const toString = Object.prototype.toString
toString.call(null) // -> '[object Null]'
toString.call([]) // -> '[object Array]'
toString.call({}) // -> '[object Object]'
toString.call(function(){}) // -> '[object Function]'
toString.call(class {}) // -> '[object Function]'
toString.call(new Date()) // -> '[object Date]'
toString.call(new RegExp()) // -> '[object RegExp]'
toString.call(new Error()) // -> '[object Error]'
// ... 省略其它类型数据的判断
问题是几乎已经得到了解决,是不是?
_type() 方法
根据之前的了解,可以使用 typeof 关键字判断原始值类型的数据(null除外),使用 Object.prototype.toString() 方法判断对象类型的值,因此我们可以整理一个 _type() 方法:
数据类型名称枚举值
以下代码中的 TYPES 对象中存储的就是根据 MDN 中细分的数对象类型归纳的实际开发中常见的(对象)数据类型的枚举值。
const TYPES = {
/* ===== Primitive data types ===== */
BIG_INT: 'bigint',
BOOLEAN: 'boolean',
NULL: 'null',
NUMBER: 'number',
UNDEFINED: 'undefined',
STRING: 'string',
SYMBOL: 'symbol',
/* ===== Keyed Collections ===== */
SET: 'set',
WEAK_SET: 'weakset',
MAP: 'map',
WEAK_MAP: 'weakmap',
/* ===== Array ===== */
ARRAY: 'array',
ARGUMENTS: 'arguments',
/* ===== Typed Arrays ===== */
DATA_VIEW: 'dataview',
ARRAY_BUFFER: 'arraybuffer',
INT8_ARRAY: 'int8array',
UNIT8_ARRAY: 'uint8array',
UNIT8_CLAMPED_ARRAY: 'uint8clampedarray',
INT16_ARRAY: 'int16array',
UNIT16_ARRAY: 'uint16array',
INT32_ARRAY: 'int32array',
UNIT32_ARRAY: 'uint32array',
FLOAT32_ARRAY: 'float32array',
FLOAT64_ARRAY: 'float64array',
BIG_INT64_ARRAY: 'bigint64array',
BIG_UINT64_ARRAY: 'biguint64array',
/* ===== Object ===== */
OBJECT: 'object',
COLLECTION: 'collection',
DATE: 'date',
ELEMENT: 'element',
ERROR: 'error',
FRAGMENT: 'fragment',
FUNCTION: 'function',
PROMISE: 'promise',
REGEXP: 'regexp',
TEXT: 'text'
}
export default TYPES
Object.prototype.toString() 输出的类型名称
以下代码中的 OBJECTS 对象,是我们为了优化 _type() 方法判断条件分支逻辑而专门整理的一个 Map 对象(不是一个真正的 Map)。利用它可以免去很多 if 或者 switch 语句的判断分支,从而大大降低代码的复杂度。
import TYPES from './types'
// Object.prototype.toString() 输出的类型名称枚举值
const OBJECTS = {
/* ===== Primitive data types ===== */
'[object Null]': TYPES.NULL,
/* ===== Keyed Collections ===== */
'[object Set]': TYPES.SET,
'[object WeakSet]': TYPES.WEAK_SET,
'[object Map]': TYPES.MAP,
'[object WeakMap]': TYPES.WEAK_MAP,
/* ===== Array ===== */
'[object Array]': TYPES.ARRAY,
'[object Arguments]': TYPES.ARGUMENTS,
/* ===== Typed Arrays ===== */
'[object DataView]': TYPES.DATA_VIEW,
'[object ArrayBuffer]': TYPES.ARRAY_BUFFER,
'[object Int8Array]': TYPES.INT8_ARRAY,
'[object Uint8Array]': TYPES.UNIT8_ARRAY,
'[object Uint8ClampedArray]': TYPES.UNIT8_CLAMPED_ARRAY,
'[object Int16Array]': TYPES.INT16_ARRAY,
'[object Uint16Array]': TYPES.UNIT16_ARRAY,
'[object Int32Array]': TYPES.INT32_ARRAY,
'[object Uint32Array]': TYPES.UNIT32_ARRAY,
'[object Float32Array]': TYPES.FLOAT32_ARRAY,
'[object Float64Array]': TYPES.FLOAT64_ARRAY,
'[object BigInt64Array]': TYPES.BIG_INT64_ARRAY,
'[object BigUint64Array]': TYPES.BIG_UINT64_ARRAY,
/* ===== Object ===== */
'[object Object]': TYPES.OBJECT,
'[object Boolean]': TYPES.OBJECT,
'[object String]': TYPES.OBJECT,
'[object Number]': TYPES.OBJECT,
'[object Date]': TYPES.DATE,
'[object Error]': TYPES.ERROR,
'[object DocumentFragment]': TYPES.FRAGMENT,
'[object Function]': TYPES.FUNCTION,
'[object NodeList]': TYPES.COLLECTION,
'[object Promise]': TYPES.PROMISE,
'[object RegExp]': TYPES.REGEXP,
'[object Text]': TYPES.TEXT
}
export default OBJECTS
_typeof() 方法实现
从 TYPES 对象我们已得知,_type() 方法并没有将 Standard built-in objects 中所有的对象类型都包含在内,而是根据实际的开发经验,将常用的数据类型基本都纳入进去了,代码如下:。
// enum/types.js 中存储的是最终显示的数据类型
import TYPES from './enum/types'
// enum/objects.js 中存储的则是使用 Object.prototype.toString() 得到对象类型的字符串
import OBJECTS from './enum/objects'
/**
* 检测数据类型,返回检测数据类型的字符串
* ========================================================================
* @method _type
* @param {*} val - 要检测的任意值
* @returns {String}
*/
const _type = (val) => {
const type = Object.prototype.toString.apply(val)
const _typeof = typeof val
let name
// HTMLElement
if (val?.tagName && val.nodeType === 1) {
name = TYPES.ELEMENT
} else {
/* ===== 原始值类型(Primitive data types) ===== */
switch(_typeof){
case 'bigint':
name = TYPES.BIG_INT
break
case 'string':
name = TYPES.STRING
break
case 'number':
name = TYPES.NUMBER
break
case 'boolean':
name = TYPES.BOOLEAN
break
case 'undefined':
name = TYPES.UNDEFINED
break
case 'symbol':
name = TYPES.SYMBOL
break
// 对象(引用)类型的数据
default:
name = OBJECTS[type]
break
}
}
// 如果出现 OBJECTS 中没有包含的枚举值,
// 则直接输出 '[object xxx]' 格式的类型字符串
return name || type
}
export default _type
可以看到,除了内置对象,针对 DOM 操作的常用对象 HTMLElement、NodeList、TextNode 和 DocuemntFragement 对象,_type() 方法也是可以识别的。_type() 方法已经足够好了,但如果需你要更加完善的数据类型检测的 JavaScript 工具库,不妨试试我的 types.js 项目。