[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]';
}

浙公網安備 33010602011771號