[JS] 数据类型与特殊值的判断方法
由于JS是弱类型语言,判断一个变量的数据类型是一个很常见的需求。
下面介绍一些常用的判断方法:
typeof操作符
typeof
可以用来判断除了null
的基本数据类型和function
,其它引用数据类型都会返回object
。
console.log(typeof "Hello"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof function(){}); // "function"
console.log(typeof null); // "object" (这是一个历史遗留的bug)
console.log(typeof []); // "object"
为什么typeof null会返回object ?
在JS的最初版本中,使用32位二进制表示栈中的变量,二进制的前三位为类型标识tag,当前三位都是0时,表示object类型。但是null被设计为32位二进制都是0,因此会被错误地识别为object类型。
由于这个错误影响范围很大,后期并没有被修复。
instanceof操作符
语法:变量 instanceof 函数
;
返回值:布尔值,变量是否是指定的构造函数的实例,即变量的原型链上是否存在指定的构造函数。
特点:只用来判断引用数据类型。
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true
console.log(new Date() instanceof Date); // true
对于基础数据类型:
1 instanceof Number ==> false
let a = new Number(1);
a instanceof Number ==> true
注意:instanceof
在跨iframe或者不同的JavaScript执行环境时可能会失效,因为每个执行环境都有独立的构造函数。
Object.prototype.toString.call
这是最通用和可靠的方法。通过Object.prototype.toString.call
方法,可以精确地判断变量的类型,不受执行环境的影响。
console.log(Object.prototype.toString.call("Hello")); // "[object String]"
console.log(Object.prototype.toString.call(42)); // "[object Number]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call([])); // "[object Array]"
console.log(Object.prototype.toString.call({})); // "[object Object]"
console.log(Object.prototype.toString.call(function(){})); // "[object Function]"
console.log(Object.prototype.toString.call(new Date())); // "[object Date]"
isArray
Array.isArray在ES5就存在了,与上述的Object.prototype.toString.call
方法相比:
- Array.isArray的兼容性没有后者好,但是考虑到IE目前已经无了,基本可以放心使用;
- Array.isArray作为原生的方法,底层实现会被引擎优化,通常比起后者的字符串比较操作性能会更好。
判断箭头函数
箭头函数的特点:
toString
方法返回函数体会包含=>
;(这个特点作为判断标准不严谨,因为普通函数的函数体可能包含带有=>
字符的语句)- 箭头函数没有
prototype
属性,而普通函数有; - 箭头函数不能被当作构造函数,因此使用
new
关键字实例化会抛出异常。
综合判断方法:
function isArrowFunction(func) {
if (typeof func !== 'function') {
return false;
}
try {
new func();
return false;
} catch (e) {
return !func.hasOwnProperty('prototype') && func.toString().includes('=>');
}
}
判断async函数
async函数的特点:
toString
方法返回的字符串带有async
(开头位置);Object.prototype.toString.call
会返回[object AsyncFunction]
;- 是
AsyncFunction
构造函数的实例。(由于在大多数环境中,AsyncFunction
无法直接访问,可以通过构建一个新的async
函数来获得这个构造函数)。
综合判断方法:
function isAsyncFunction(func) {
if (typeof func !== 'function') {
return false;
}
const AsyncFunction = (async function() {}).constructor;
return func instanceof AsyncFunction ||
Object.prototype.toString.call(func) === '[object AsyncFunction]' ||
func.toString().trim().startsWith('async');
}
判断class
常见方法:
- 使用
typeof
和Function.prototype.toString
:通过typeof
检查是否是函数,然后通过toString
检查字符串表示形式中是否包含class
关键字; - 检查原型链:类的原型链上通常会有
constructor
属性,并且这个constructor
属性指向类自身; - 使用
new
检查:类不能在没有new
关键字的情况下调用,而函数可以。
综合方法:
function isClass(func) {
if (typeof func !== 'function') {
return false;
}
try {
func();
return false;
} catch (e) {
if (e.message.includes('Class constructor') || e.message.includes('class constructors')) {
return true;
}
return /^class\s/.test(Function.prototype.toString.call(func));
}
}
示例文件
// is.js
/**
* 判断是否为字符串
* @param value - 需要判断的值
* @returns boolean
*/
function isString(value) {
return Object.prototype.toString.call(value) === '[object String]';
}
/**
* 判断是否为数字
* @param value - 需要判断的值
* @returns boolean
*/
function isNumber(value) {
return Object.prototype.toString.call(value) === '[object Number]';
}
/**
* 判断是否为布尔值
* @param value - 需要判断的值
* @returns boolean
*/
function isBoolean(value) {
return Object.prototype.toString.call(value) === '[object Boolean]';
}
/**
* 判断是否为 undefined
* @param value - 需要判断的值
* @returns boolean
*/
function isUndefined(value) {
return Object.prototype.toString.call(value) === '[object Undefined]';
}
/**
* 判断是否为 null
* @param value - 需要判断的值
* @returns boolean
*/
function isNull(value) {
return Object.prototype.toString.call(value) === '[object Null]';
}
/**
* 判断是否为数组
* @param value - 需要判断的值
* @returns boolean
*/
function isArray(value) {
return Array.isArray(value);
// return Object.prototype.toString.call(value) === '[object Array]';
}
/**
* 判断是否为对象
* @param value - 需要判断的值
* @returns boolean
*/
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
/**
* 判断是否为函数
* @param value - 需要判断的值
* @returns boolean
*/
function isFunction(value) {
return Object.prototype.toString.call(value) === '[object Function]';
}
/**
* 判断是否为日期
* @param value - 需要判断的值
* @returns boolean
*/
function isDate(value) {
return Object.prototype.toString.call(value) === '[object Date]';
}
/**
* 判断是否为正则表达式
* @param value - 需要判断的值
* @returns boolean
*/
function isRegExp(value) {
return Object.prototype.toString.call(value) === '[object RegExp]';
}
/**
* 判断是否为错误对象
* @param value - 需要判断的值
* @returns boolean
*/
function isError(value) {
return Object.prototype.toString.call(value) === '[object Error]';
}
/**
* 判断是否为 Symbol
* @param value - 需要判断的值
* @returns boolean
*/
function isSymbol(value) {
return Object.prototype.toString.call(value) === '[object Symbol]';
}
/**
* 判断是否为 Promise
* @param value - 需要判断的值
* @returns boolean
*/
function isPromise(value) {
return Object.prototype.toString.call(value) === '[object Promise]';
}
/**
* 判断是否为 Set
* @param value - 需要判断的值
* @returns boolean
*/
function isSet(value) {
return Object.prototype.toString.call(value) === '[object Set]';
}
/**
* 判断是否为 Map
* @param value - 需要判断的值
* @returns boolean
*/
function isMap(value) {
return Object.prototype.toString.call(value) === '[object Map]';
}
/**
* 判断是否为 箭头函数
* @param value - 需要判断的值
* @returns boolean
*/
function isArrowFunction(func) {
if (typeof func !== 'function') {
return false;
}
try {
new func();
return false;
} catch (e) {
return !func.hasOwnProperty('prototype') && func.toString().includes('=>');
}
}
/**
* 判断是否为 async函数
* @param value - 需要判断的值
* @returns boolean
*/
function isAsyncFunction(func) {
if (typeof func !== 'function') {
return false;
}
const AsyncFunction = (async function() {}).constructor;
return func instanceof AsyncFunction ||
Object.prototype.toString.call(func) === '[object AsyncFunction]' ||
func.toString().trim().startsWith('async');
}
/**
* 判断是否为 class
* @param value - 需要判断的值
* @returns boolean
*/
function isClass(func) {
if (typeof func !== 'function') {
return false;
}
try {
func();
return false;
} catch (e) {
if (e.message.includes('Class constructor') || e.message.includes('class constructors')) {
return true;
}
return /^class\s/.test(Function.prototype.toString.call(func));
}
}
/**
* 判断是否为 空对象
* @param value - 需要判断的值
* @returns boolean
*/
function isEmptyObject(value) {
return isObject(value) && Object.keys(value).length === 0;
}
// 导出函数
module.exports = {
// type
isString,
isNumber,
isBoolean,
isUndefined,
isNull,
isArray,
isObject,
isFunction,
isDate,
isRegExp,
isError,
isSymbol,
isPromise,
isSet,
isMap,
isArrowFunction,
isAsyncFunction,
isClass,
// value
isEmptyObject,
};
值比较
除了类型比较,JS里有一些值也是经常需要判断的。
NaN、Infinitity、Integer、safeInteger
这些和数值相关的判断都在Number的静态方法里了。
Number.isNaN(value); // 是否NaN
Number.isFinite(value); // 是否有限数值
function isInfinitiy(value){ // 是否是无穷大
return !Number.isFinite(value);
// 另一种写法
return value === Infinity || value === -Infinity;
}
Number.isInteger(value); // 判断整数
Number.isSafeInteger(value); // 判断安全整数
判断空对象
空对象指的是不包含任何可枚举属性的对象。
function isEmptyObject(value) {
return isObject(value) && Object.keys(value).length === 0;
}
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}