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检测原始值、引用值、属性

  • 相关阅读:
    svn cleanup failed–previous operation has not finished 解决方法
    开源SNS社区系统推荐
    从网络获取图片本地保存
    MS SQL Server 数据库连接字符串
    KeepAlive
    Configure Git in debian
    sqlserver query time
    RPi Text to Speech (Speech Synthesis)
    SQL Joins with C# LINQ
    search or reseed identity columns in sqlserver 2008
  • 原文地址:https://www.cnblogs.com/dyqblog/p/10430788.html
Copyright © 2011-2022 走看看