在 JavaScript 中检测数据类型

我在《JavaScript 数据类型(值类型和引用类型)》一文中简单的介绍了 JavaScript 中的数据类型,今天就来讲讲在 JavaScript 如何去检测具体的数据类型。

typeof 操作符

JavaScript 中5个基础的值类型是:Undefined、Null、Boolean、Number 和 String,这个在介绍 JavaScript 数据类型的时候已经介绍过。JavaScript 中有一个内置的 typeof 操作符可以用来检测数据类型,并且可准确的检测出 Undefined、Boolean、Number 和 String 这4中类型的值。

typeof robert; // -> 'undefined'
typeof false; // -> 'boolean' 
typeof 'Robert Yao'; // -> 'string'
typeof 4; // -> 'number'

因此我们可以使用 typeof 操作符做判断,检测值类型数据。

检测值类型数据

上面已经介绍过了,可以使用 typeof 操作符来检测值类型数据(Undefined、Boolean、Number 和 String)。

isUndefined

// 判断是否未定义
function isUndefined ( o ) {
    return typeof o === 'undefined';
}

注意,isUndefined 方法只能检测声明过,但未初始化的变量,而不能用来检测连声明都没有声明过的变量。

var robert;

isUndefined(robert); // -> true
isUndefined(yao); // -> 报错 ‘Uncaught ReferenceError: yao is not defined’

如果确实要检测一个不确定是否声明过的变量是否为 ‘undefined’,就只能使用 typeof 操作符直接判断了:typeof yao

isBoolean

// 判断是否为布尔值
function isBoolean ( o ) {
    return typeof o === 'boolean';
}

isString

function isString ( o ) {
    return typeof o === 'string';
}

isNumber

// 判断是否为数值
function isNumber ( o ) {
    return typeof o === 'number' && isFinite( o );
}

isNumber 方法的判断除了使用 typeof 操作符,还添加了一个 isFinite( o ) 的判断。这么处理时应为在 JavaScript 中有一个神奇的 NaN (Not a Number)值得存在。NaN 值表示它不是一个数值(Number),但是使用 typeof 操作判断它:typeof NaN,得到的结果却又是 ‘number’。而且这个值更特殊的地方是它不等于任何值甚至是 NaN 本身。

typeof NaN; // -> 'number'
NaN === NaN; // -> false

为了过滤掉 NaN 这个对实际数学运算中没有意义的数值,我们需要使用 isFinite()(用于检查其参数是否是无穷大)函数来协助(同时也过滤掉正负无穷大这两个数)。

用 typeof 操作符检测 null 的时候,得到的却是 ‘object’(对象)。

typeof null; // -> 'object'

所以要判断变量值是否为 null 我们就需要另想办法了。

isNull

// 判断是否为空值
function isNull ( o ) {
    return o === null;
}

其实判断数据是否为 null,直接用值和 null 做恒等比较就行了。这只我这里要说明的是,在 JavaScript 中作相等或者不相等的比较的时候,建议使用恒等比较符 ===!==

isSymbol

// 判断是否为符号类型
function isSymbol ( o ) {
    return Object.prototype.toString.apply( o ) === '[object Symbol]';
}

实际上除了之前介绍的 Undefined、Null、Boolean、Number 和 String 5中值类型数据外,JavaScript 还新增了一个 symbol 类型(具体内容请参考:MDN 文档中关于 Symbol 的章节)的值类型数据。

判断 symbol 类型我们使用了 Object.prototype.toString.apply( o ) 的方式返回对象 toString 后的类型。在判断引用类型的值的时候,我们也会一直使用这个技巧来判断引用类型的值的具体数据类型。

检测引用类型数据

在检测完值类型的数据后,接下来就要介绍如何判断引用类型的值。在介绍 JavaScript 数据类型的文章中午我们知道了,引用类型的值基本都是对象(Object)。本文主要介绍如何检测 Array, Function, Object, Date, Error, RegExp 这几个常用的对象的数据类型。

isArray

// 判断是否为数组
function isArray ( o ) {

    if ( Array.isArray ) {
        return Array.isArray( o );
    }
    else {
        return Object.prototype.toString.apply( o ) === '[object Array]';
    }
}

isArray 方法会判断系统是否存在 Array.isArray 方法,如果存在就会直接调用系统原生的方法来判断,性能会更好。如果没有原生的 isArray 方法,就只能通过前面介绍的调用 Object.prototype.toString 方法来获得检测对象 toString 后的数据类型的字符串来判断数据类型了。

isDate

// 判断是否为日期类型
function isDate ( o ) {
    return Object.prototype.toString.apply( o ) === '[object Date]' && o.toString() !== 'Invalid Date' && !isNaN( o );
}

JavaScript 中的 Date(日期)类型的数据都是通过创建一个 Date 对象的实例:new Date() 实现的。但是 Date() 构造函数接受的参数类型是有限制的,如果传入函数(Function)类型的值,或者是 NaN,就返回 Invalid Date 的值,而不是一个正确的日期。

date = new Date('2'); // -> 'Thu Feb 01 2001 00:00:00 GMT+0800 (中国标准时间)'
date = new Date(alert); // -> 虽然不报错,但是返回的是 Invalid Date
date = new Date(NaN); // -> Invalid Date

所以为了排除这些不合法的日期类型数据,需要添加 o.toString() !== 'Invalid Date' && !isNaN( o ); 的判断。

isError

// 判断是否为错误
function isError ( o ) {
    return Object.prototype.toString.apply( o ) === '[object Error]';
}

isFunction

// 判断是否为函数
function isFunction ( o ) {
    return typeof o === 'function' || Object.prototype.toString.apply( o ) === '[object Function]';
}

isObject

// 判断是否为对象
function isObject ( o ) {
    return (o && (typeof o === 'object' || isFunction( o ))) || false;
}

JavaScript 中函数是第一的对象,在 JavaScript 中我们用函数来表示对象(类),也就是说函数既是对象,也是函数,具有“双重身份”。

isRegExp

// 判断是否为正则表达式
function isRegExp ( o ) {
    return Object.prototype.toString.apply( o ) === '[object RegExp]';
}

到这里,JavaScript 中内置的数据类型的检测方法就介绍完了。接下来会介绍另外一些常用的数据检测方法。

数组相关的检测

isArguments

// 判断是否为 Arguments(参数)对象
function isArguments ( o ) {
    // ES5 严格模式下不允许使用 callee
	// return typeof o === 'object' && o.length && o.length > 0 && ('callee' in o);
    return Object.prototype.toString.call( o ) === '[object Arguments]';
}

isHTMLCollection

// 判断是否为 HTMLCollection(NodeList)
function isHTMLCollection ( o ) {
    return typeof o === 'object' && o.length && o.length > 0 && ('item' in o);
}

Arguments对象 和 HTMLCollection(NodeList) 对象和 Array(数组)很像,都可以使用 for 循环遍历,也都有 length 属性。只是它们没有 Array 对象的(例如:push,shift)方法。

isArrayLike

// 判断是否为类似数组的数据类型
function isArrayLike ( o ) {
    return isArray( o ) || isArguments( o ) || isHTMLCollection( o );
}

isArrayLike 是我们经常会用到的一个方法。前面提到了 Arguments对象 和 HTMLCollection(NodeList) 对象与 Array(数组)很像,当我们使用 isArrayLike 方法判断得知它是像 Array 对象的数据,我们就可以使用 Array.prototype.method.apply(object) 的技巧,把检测对象当作数组使用,直接应用 Array 对象内置的方法操作数据了。

数值相关的检测

isInteger

// 判断是否为整数
function isInteger ( o ) {
    if ( !isNumber( o ) || isNaN( o ) ) {
        return false;
    }

    if ( Number.isInteger ) {
        return Number.isInteger( o );
    }

    return Number( o ) === o && o % 1 === 0;
}

isFloat

// 判断是否为浮点数
function isFloat ( o ) {
    return isNumber( o ) && !isNaN( o ) && Number( o ) === o && o % 1 !== 0;
}

isEven

// 判断是否为偶数
function isEven ( o ) {
    return isInteger( o ) && o % 2 === 0;
}

isOdd

// 判断是否为奇数
function isOdd ( o ) {
    return isInteger( o ) && o % 2 !== 0;
}

isNumeric

// 判断是否为(可以转化为)数值类型的值
// 代码来源: jQuery 3.x
function isNumeric ( o ) {
    // parseFloat NaNs numeric-cast false positives ("")
    // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
    // subtraction forces infinities to NaN
    return ( isNumber( o ) || typeof o === 'string' ) && !isNaN( o - parseFloat( o ) );
}

对象相关的检测

hasOwnProperty

// 判断对象(o)是否拥有某个属性(prop)
function hasOwnProperty ( o, prop ) {

    if ( Object.prototype.hasOwnProperty ) {
        return o && o.hasOwnProperty && o.hasOwnProperty( prop );
    }
    else {
        return typeof o[ prop ] !== 'undefined' && o.constructor.prototype[ prop ] !== o[ prop ];
    }
}

isEmptyObject

// 判断对象是否为空对象({})
// 代码来源: jQuery 3.x
function isEmptyObject ( obj ) {

    /* eslint-disable no-unused-vars */
    // See https://github.com/eslint/eslint/issues/6125
    var prop;

    for ( prop in obj ) {
        if ( hasOwnProperty( obj, prop ) ) {
            return false;
        }
    }

    return true;
}

isPlainObject

// 判断是否为一个普通对象
// 代码来源: jQuery 3.x
function isPlainObject ( o ) {
    var OP = Object.prototype,
        toString = OP.toString,
        proto,
        Ctor;

    // Detect obvious negatives
    if ( !o || !isObject( o ) ) {
        return false;
    }

    proto = o.prototype;

    // Objects with no prototype (e.g., `Object.create( null )`) are plain
    if ( !proto ) {
        return true;
    }

    // Objects with prototype are plain if they were constructed by a global Object function
    Ctor = hasOwnProperty( proto, 'constructor' ) && proto.constructor;

    return isFunction( Ctor ) && toString.apply( Ctor ) === {}.hasOwnProperty.toString.apply( Object );
}

函数相关的检测

isConstructor

// 判断是否为构造函数
function isConstructor ( fn ) {
    var instance;

    if ( !isFunction( fn ) ) {
        return false;
    }

    try {
        instance = new fn();
    }
    catch ( err ) {
        
        if ( err.message.indexOf( 'is not a constructor' ) ) {
            return false;
        }
    }

    return (instance.constructor === fn && instance instanceof fn) || (instance.constructor === Object && instance instanceof Object);
}

isNativeFunction

// 判断是否为系统内置函数
function isNativeFunction ( fn ) {
    return isFunction( fn ) && (/{\s*\[native code\]\s*}/).test( '' + fn );
}

DOM 相关的检测

isHTMLElement

// 判断是否为 DOM 元素节点
function isHTMLElement ( o ) {
    return typeof o === 'object' && o.nodeName && o.nodeType === 1;
}

isHTMLTextNode

// 判断是否为文本节点
function isHTMLTextNode ( o ) {
    return typeof o === 'object' && o.nodeName && o.nodeType === 3;
}

isXML

// 判断是否为 XML 
function isXML ( o ) {
    var documentElement = o && (o.ownerDocument || o).documentElement;

    return documentElement ? documentElement.nodeName !== 'HTML' : false;
}

字符串相关的检测

isBase64

// 判断是否为基于BASE64编码的字符串
function isBase64 ( o ) {
    var REGEXP_BASE64 = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/;

    return typeof o === 'string' && (!o.length || REGEXP_BASE64.test( o ));
}

isBlank

// 判断是否全是空格
function isBlank ( o ) {

    if ( !isString( o ) ) {
        return false;
    }

    return /^\s*$/.test( o );
}

isEmpty

// 判断是否为空字符串
function isEmpty ( o ) {
    return o === '';
}

isHex

// 判断是否为16进制的编码(可以检测CSS色值的编码是否正确)
function isHex ( o ) {
    var REGEXP_HEX = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;

    return typeof o === 'string' && REGEXP_HEX.test( o );
}

isJSON

// 判断是否 JSON 格式的字符串
function isJSON ( o ) {

    if (  !isString(o)  || isBlank(o) || !o ) {
        return false;
    }

    o = o.replace( /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@' );
    o = o.replace( /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']' );
    o = o.replace( /(?:^|:|,)(?:\s*\[)+/g, '' );

    return (/^[\],:{}\s]*$/).test( o );
}

到此为止基本上所有的常用的数据相关的检测就都给大家介绍了,希望能对你的开发或者学习有帮助。我也将这些方法封装了一下,作为 typeofit.js 发布到了 GitHub 上,也欢迎大家试用,返回意见和BUG。

SHARE THIS PAGE

免责声明:本站文章中的观点都是作者个人观点,并没有以任何方式反映他所属机构的意见。

发表评论