[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类型。

由于这个错误影响范围很大,后期并没有被修复。

👉harmony:typeof_null [ES Wiki] (archive.org)

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作为原生的方法,底层实现会被引擎优化,通常比起后者的字符串比较操作性能会更好。

判断箭头函数

箭头函数的特点:

  1. toString方法返回函数体会包含=>;(这个特点作为判断标准不严谨,因为普通函数的函数体可能包含带有=>字符的语句)
  2. 箭头函数没有prototype属性,而普通函数有;
  3. 箭头函数不能被当作构造函数,因此使用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函数的特点:

  1. toString方法返回的字符串带有async(开头位置);
  2. Object.prototype.toString.call会返回[object AsyncFunction]
  3. 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

常见方法:

  1. 使用 typeofFunction.prototype.toString:通过 typeof 检查是否是函数,然后通过 toString 检查字符串表示形式中是否包含 class 关键字;
  2. 检查原型链:类的原型链上通常会有 constructor 属性,并且这个 constructor 属性指向类自身;
  3. 使用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]';
}

热门相关:我真的是正派   第99次离婚   调教初唐   不伦小姐2   我真不是开玩笑