zoukankan      html  css  js  c++  java
  • javascript 数据类型 -- 检测

    一、前言

      在上一篇博文中 Javascript 数据类型 -- 分类 中,我们梳理了 javascript 的基本类型和引用类型,并提到了一些冷知识。大概的知识框架如下:

         

      这篇博文就讲一下在写代码的过程中,通常怎么检测这些类型。

    二、检测

      总的来说,我们有4种可检测数据类型的方法, typeof 运算符、  constructor 属性、 instanceof 运算符、 prototype.isPrototypeOf 方法、 Object.prototype.toString.call 方法、 in 操作符、 hasOwnProperty 方法及 isNaN() 、 Array.isArray() 等几种特殊检测方式。每种方法各有优劣,实际运用时还要结合着使用。

      typeof 运算符

        在上一篇博文中,我们已经用到了 typeof 运算符来检测类别,这里就简单得总结一下它的结果和结论。

    typeof '123'                    // string
    typeof String('123')            // string
    typeif new String('123')        // string
    
    typeof 123                      // number
    typeof Number(23.4)             // number
    typeof new Number(2.3e4)        // number
    typeof NaN                      // number
    typeof Infinity                 // number
    
    typeof false                    // boolean
    typeof Boolean(false)           // boolean
    typeof new Boolean(false)       // boolean
    
    var mySymbol = Symbol()
    typeof mySymbol                 // symbol
    
    var a;
    typeof a                        // undefined,定义但未初始化
    typeof b                        // undefined,未定义
    
    var div = document.getElementById('abc')
    console.log(div)                // null
    typeof div                      // object
    
    var Fn = function () {}
    var classC = class C {}
    typeof Fn                       // function
    typeof classC                   // function,类在本质上还是函数
    
    typeof {}                       // object
    typeof []                       // object
    typeof /w+/                    // object
    typeof new Map()                // object
    typeof new Set()                // object
    
    (function () {
      return typeof arguments
    })()                 // object

        如上所示, typeof 运算符的语法是 typeof variable 。因是运算符的原由,所以不推荐使用加括号的写法,即( typeof (variable) ) ,以免与方法混淆。运算后的返回值有:

      • "boolean"     :布尔型;
      • "string"      :字符型;
      • "number"      :数值型(整数/浮点数值/NaN);
      •  "symbol"     :Symbol 型
      • "undefined" :变量未定义,或未初始化;
      • "object"     :引用型或null;
      • "function"  :函数型;

        由上可知, typeof 运算符对于大部分基本类型的判断还是准确的,特别是 string 、 number 、 boolean 和 symbol ,对于大部分的引用类似会返回 object ,函数虽然也属于引用类型,但由于其具有很对有别于一般引用类型的特性,所以单独判断为 "function" 。若是针对 NaN 进行检测,可以使用全局方法 isNaN(variable) 更为精准。由此,我们可以编写一个统一的方法,先将 undefined 和 null 类型检测出来,再检测其他基本类型。

    function is(value, type) {
        if (value == null) {
            return value === type
        } else if (typeof value === 'number' && type === 'isNaN') {
            return isNaN(value)
        }
        return typeof value === type
    }
    
    var x;
    is(x, undefined)                // true
    
    var div = document.getElementById('div')
    is(div, null)                   // true
    
    var y = 2 + undefined
    is(y, 'isNaN')                  // true
    
    is(3, 'number')                 // true
    is('3', 'string')               // true
    is(true, 'boolean')             // true
    is(Symbol(), 'symbol')          // true
    is(function(){}, 'function')    // true
    
    is(new String('s'), 'object')   // true
    is(new Map(), 'object')         // true
    is({}, 'object')                // true

      

      constructor 属性

        由于 typeof 运算符并不能准确地检测出引用类型的值,所以我们可以试试 constructor 属性。

        当一个函数  foo 被定义是,javascript 会给 foo 函数添加 prototype 原型,然后再在 prototype 上添加一个 contructor 属性,并让其指向 foo 的引用,如下所示:

        当执行 var bar = new foo() 时, foo 被当成了构造函数, bar 是 foo 的实例对象,此时 foo 原型上的 constructor 传递到了 bar 上,因此 bar.constructor == foo (返回的是一个true)

        可以看出,JavaScript在函数 foo 的原型上定义了 constructor ,当 foo 被当作构造函数用来创建对象时,创建的新对象就被标记为 foo 类型,使得新对象有名有姓,可以追溯。所以 constructor 属性可以用来检测自定义类型。同理,JavaScript中的数据类型也遵守这个规则:

    '3'.constructor === String                 // true,由于包装对象的缘故,而拥有了 construtor 属性
    String('').constructor === String          // true
    new String('').constructor === String      // true
    
    (3).constructor === Number                 // true,与 string 同理
    Number(3).constructor === Number           // true
    new Number(3).constructor === Number       // true
    
    false.constructor === Boolean              // true,与 string 同理
    Boolean(false).constructor === Boolean     // true
    new Boolean().constructor === Boolean      // true
    
    Symbol().constructor === Symbol            // true
    
    ({}).constructor == Object                 // true
    [].constructor === Array                   // true
    (function(){}).constructor === Function    // true
    new Date().constructor === Date            // true
    new Error().constructor === Error          // true
    new RegExp().constructor === RegExp        // true
    new Map().constructor === Map              // true
    new Set().constructor === Set              // true
    
    document.constructor === HTMLDocument      // true
    window.constructor === Window              // true
    
    null.constructor === Object                // Uncaught TypeError: Cannot read property 'constructor' of null
    undefined.constructor === Object           // Uncaught TypeError: Cannot read property 'constructor' of undefined

        由上可见, constructor 属性对于大部分值都是能准确得出其类型的,特别是几个基本类型,也得益于包装对象的缘故,可以被准确地检测出来。只有 undefined 和 null 由于没有包装对象,不能进行转换,所以不能被检出。因此,上面的判断函数可以改进为:

    function is(value, type) {
        if (value == null) {
            return value === type
        // } else if (value.constructor === 'number' && type === 'isNaN') {
        } else if (value.constructor === Number && type === 'isNaN') {
            return isNaN(value)
        }
        // return typeof value === type
        return value.constructor === type
    }
    
    var x;
    is(x, undefined)                // true
    
    var div = document.getElementById('div')
    is(div, null)                   // true
    
    var y = 2 + undefined
    is(y, 'isNaN')                  // true
    
    is(3, Number)                 // true
    is('3', String)               // true
    is(true, Boolean)             // true
    is(Symbol(), Symbol)          // true
    is({}, Object)                // true
    is([], Array)                 // true
    is(new Map(), Map)            // true
    is(new Set(), Set)            // true
    is(new Date(), Date)          // true
    is(new Error(), Error)        // true
    is(new RegExp(), RegExp)      // true
    is(function(){}, Function)    // true
    is(document, HTMLDocument)    // true
    is(window, Window)            // true

      当然,根据 construtor 方法的原理可以知道,在判断自定义类型时,由于 prototype 是可以被认为修改的,原有的 construtor 也就会被指向新 prototype 的构造器上。因此,为了规范,在重新定义原型时,一定要给 constructor 重新赋值,以保证构造器不被改变。除非是有预期的希望它改变。

    function Animal(){}
    
    var cat = new Animal()
    is(cat, Animal)                  // true
    
    Animal.prototype = {}
    var dog = new Animal()
    is(dog, Animal)                 // false
    is(dog, Object)                 // true
    
    Animal.prototype.constructor = Animal
    var tiger = new Animal()
    is(tiger, Animal)               // true
    is(tiger, Object)               // false

      instanceof 运算符

        在上一节中我们说到,如果手动更改构造函数的 prototpye 的话, constructor 方法就会失效。这种情况下,除了手动为构造函数指定 constructor 外,还有一种方法就是使用 instanceof 运算符。

    cat instanceof Animal         // false
    dog instanceof Animal         // true
    tiger instanceof Animal       // true

        在上面的代码中, cat instanceof Animal 输出的是  false ,为什么呢?

        这是因为 instanceof 检测是是对象的原型链中是否包含某个构造函数的 prototype 属性。即 instanceof 检测的是原型。在上面的代码中, Animal 在 cat 实例化之后,将 prototype 属性改成 {},所以  cat._proto_ 中,已经不包括 Animal.prototype 了,所以输出为 false 。所以  instanceof 运算符也存在自定义类型的继承问题。但对于没被修改过 prototype 属性的内置对象而言, instanceof 方法还是可以判断的。

    ({}) instanceof Object;               // true
    [] instanceof Array;                  // true
    (function(){}) instanceof Function;   // true
    /w+/ instanceof RegExp;              // true
    new Date instanceof Date;             // true
    new Error instanceof Error;           // true
    new Map instanceof Map;               // true
    new Set instanceof Set;               // true
    
    // 由于默认情况下,对象都是继承自 Object,所以引用类型都是 Object 的实例。
    [] instanceof Object                  // true

        由于 instanceof  是根据原型链来进行检查的,所以适用于任何引用类型。而基础类型并没有原型,所以并不能检测出来。对于有包装对象的几个继承类型, instanceof 也不会隐性转换,除非用包装对象进行显性转换才可检测出来。

    3 instanceof Number;                  // false
    Number(3) instanceof Number           // false
    new Number(3) instanceof Number       // true
    
    '3' instanceof String;                // false
    String('3') instanceof String         // false
    new String('3') instanceof String     // true
    
    true instanceof Boolean;              // false
    Boolean(true) instanceof Boolean      // false
    new Boolean(true) instanceof Boolean  // true
    
    Symbol() instanceof Symbol;           // false
    Object(Symbol()) instanceof Symbol    // true
    
    // 特别的,虽然 typeof null 等于 object,但 null 并不是 object 的实例
    null instanceof Object;               // false

      prototype.isPrototypeOf() 方法

        方法用于测试一个对象是否存在于另一个对象的原型链上,用法为 XXX.prototype.isPrototypeOf(instance) 。

    Object.prototype.isPrototypeOf({});                  // true
    Array.prototype.isPrototypeOf([]);  ;                // true
    Function.prototype.isPrototypeOf(function(){});      // true
    RegExp.prototype.isPrototypeOf(/w+/);               // true
    Date.prototype.isPrototypeOf(new Date);              // true
    Error.prototype.isPrototypeOf(new Error);            // true
    Map.prototype.isPrototypeOf(new Map);                // true
    Set.prototype.isPrototypeOf(new Set);                // true
    
    // 由于默认情况下,对象都是继承自 Object,所以引用类型都是 Object 的实例。
    Object.prototype.isPrototypeOf(function(){})         // true
    
    Number.prototype.isPrototypeOf(3);                   // false
    Number.prototype.isPrototypeOf(Number(3));           // false
    Number.prototype.isPrototypeOf(new Number(3));       // true
    
    String.prototype.isPrototypeOf('3');                 // false
    String.prototype.isPrototypeOf(String('3'));         // false
    String.prototype.isPrototypeOf(new String('3'));     // true
    
    Boolean.prototype.isPrototypeOf(true);               // false
    Boolean.prototype.isPrototypeOf(Boolean(true);       // false
    Boolean.prototype.isPrototypeOf(new Boolean(true));  // true
    
    Symbol.prototype.isPrototypeOf(Symbol());            // false
    Symbol.prototype.isPrototypeOf(Object(Symbol()));    // true
    
    // 特别的,虽然 typeof null 等于 object,Object 并不在 null 的原型链上
    Object.prototype.isPrototypeOf(null);                // false

        由上可知, prototype.isPrototypeOf() 方法与 instanceof 操作符走的是互相逆向的两条路, instanceof 是从实例出发,查找实例上是否有某构造函数的 prototype 属性,而 prototype.isPrototypeOf() 则是从构造函数出发,寻找该构造函数是否在某个已存在的实例的原型链上。

        故而,如果 A instanceof B 为真,则 B.prototype.isPrototypeOf(A) 也一定为真。

      Object.prototype.toString.call() 方法

        在最新的ES6规范中,关于 Object.prototype.toString() 方法是这么规定的:

        也就是说,如果上下文对象为 null 和 undefined ,返回 "[object Null]" 和 "[object Undefined]" ,如果是其他值,先将其转为对象,然后依次检测数组、字符串、arguments对象、函数及其它对象,得到一个内建的类型标记,最后拼接成 "[object Type]" 这样的字符串。由此可见, Object.prototype.toString.call() 方法是根正苗红的用来检测内置对象类型的方法。

    var _toString = Object.prototype.toString;
    
    function is(value, typeString) {
        // 获取到类型字符串
        var stripped = _toString.call(value).replace(/^[objects|]$/g, '');
        if (stripped === 'Number' && typeString === 'isNaN') {
            return isNaN(value);
        }
        return stripped === typeString;
    }
    
    var x;
    is(x, 'Undefined')              // true
    
    var div = document.getElementById('div')
    is(div, 'Null')                 // true
    
    var y = 2 + undefined
    is(y, 'isNaN')                  // true
    
    is(3, 'Number')                 // true
    is('3', 'String')               // true
    is(true, 'Boolean')             // true
    is(Symbol(), 'Symbol')          // true
    is({}, 'Object')                // true
    is([], 'Array')                 // true
    is(new Map(), 'Map')            // true
    is(new Set(), 'Set')            // true
    is(new Date(), 'Date')          // true
    is(new Error(), 'Error')        // true
    is(new RegExp(), 'RegExp')      // true
    is(function(){}, 'Function')    // true
    is(document, 'HTMLDocument')    // true
    is(window, 'Window')            // true

        但是由于定义的原因, Object.prototype.toString.call() 对于自定义类型就心有余而力不足了。所以,对于自定义类型就只能借助上面所提到的 contructor 属性或者 instanceof 运算符来检测了。

      特殊方式

        isNaN():如上所述,专门用来判断 NaN 的数据,用法是 isNaN(variable) ;

        Array.isArray():用于确定传递的值是不是 Array。

    // 当检测Array实例时, Array.isArray 优于 instanceof,因为Array.isArray能检测iframes。
    var iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    xArray = window.frames[window.frames.length-1].Array;
    var arr = new xArray(1,2,3); // [1,2,3]
    
    // Correctly checking for Array
    Array.isArray(arr);  // true
    // Considered harmful, because doesn't work though iframes
    arr instanceof Array; // false
    
    //在兼容性方面,Array.isArray在IE9-浏览器上没有这个方法,所以我们可以改写下方法以兼容低版本浏览器
    
    if (!Array.isArray) {
      Array.isArray = function(arg) {
        return Object.prototype.toString.call(arg) === '[object Array]';
      };
    }
    
    // or
    var isArray = function(arr){
       return typeof Array.isArray === "function" ? Array.isArray(arr) : Object.prototype.toString.call(arr) === "[object Array]";
    };
    
    var arr = [1, 2, 3, 4];
    console.log(isArray(arr));                 // true
    console.log(Array.isArray(arr));        // true
    

      

    三、总结及知识结构图

      由此,简单总结下:

      •  Object.prototype.toString.call() :覆盖范围广,特别是对于JS内置数据类型,无论基本类似还是引用类型都适用。缺点是不能检测自定义类型,需要配合用到 instanceof 方法。
      •  constructor :通过原型链的继承实现检测,对大部分基本类似和引用类型适用,特别是改进后的 is 函数。但也是原型继承的原因,对于自定义类型的检测不太稳定,可配合 instanceof 方法使用。
      •  instanceof :因为是通过原型链检测的,所以仅适用于引用类型的检测。
      •  prototype.isPrototypeOf() :  instanceof 操作符的逆向操作,结论与 instanceof 操作符相同,同样仅适用于引用类型的检测。
      •  typeof :适用于基本类型的检测,对于 null 的检测结果是 object ,对于函数的检测结果是 function 。
      •  isNaN :检测传入值是不是 NaN ,也是该类数据的唯一精准检测方法。
      •  Array.isArray :检测传入值是不是 Array ,在跨 iframe 时,使用优先级高于 instanceof 操作符。

     

    四、参考及拓展资料

      JavaScript系列文章:不能不看的数据类型检测

      JavaScript中数据类型转换

      JavaScript检测原始值、引用值、属性

  • 相关阅读:
    Java 获取指定日期的方法总结
    【Struts2+Spring3+Hibernate3】SSH框架整合实现CRUD_1.0
    struts2标签Iterator迭代时获取下标
    【转】svn 修改log信息
    pppoe移植到arm上 2.0
    镜像制作
    proc增加节点的一个例子
    libcurl库的使用
    sed & awk Manuals
    用一个函数保存文件
  • 原文地址:https://www.cnblogs.com/dyqblog/p/10430788.html
Copyright © 2011-2022 走看看