转载

JavaScrit的变量:如何检测变量类型

在《变量值的数据类型》一文中,了解到了JavaScript的变量主要有基本类型( undefinednullbooleannumberstring , ES6中还新增了 Symbol )和引用类型(对象、数组、函数)。但在JavaScript中 用户定义的类型( object )并没有类的声明,因此 继承关系 只能通过 构造函数和原型链接 来检查 。而在这篇文章中,主要整理了在JavaScript中如何检测一个变量的类型。

在JavaScript中常见的类型检查手段主要有: typeofinstanceofconstructortoString 几种。接下来主要看看这几种类型检查手段的使用与区别之处。

typeof

typeof 操作符返回的是类型字符串,它的返回值有以下几种取值:

类型 结构
Undefined "undefined"
布尔值 "boolean"
数值 "number"
字符串 "string"
Symbol "symbol"
Null "object"
宿主对象(JS环境提供的,比如浏览器) Implementation-dependent
函数对象 "function"
任何其他对象 "object"

来看看代码:

typeof 37;           // => "number" typeof "w3cplus";    // => "string" typeof true;         // => "boolean" typeof Symbol();     // => "symbol" typeof undefined;    // => "undefined" typeof {};           // => "object" typeof function (){};// => "function"

Chrome浏览器运行结果如下图所示:

JavaScrit的变量:如何检测变量类型

前面的表格中有一项也显示了,在JavaScript中,使用 typeof 做类型检测,其返回的都说是一个 object ,比如:

typeof ["w3cplus","大漠"]; typeof new Date(); typeof new String("w3cplus"); typeof new function (){}; typeof /test/i;

上面的代码在Chrome浏览器的调试工具中返回的都是 object

JavaScrit的变量:如何检测变量类型

另外对于 Nulltypeof 检测返回的值也是一个 object

typeof null; // => "object"

据说这是 typeof 的一个知名Bug。先忽略其是不是 typeof 的bug,在JavaScript中, null 也是基本数据类型之一,它的类型显然是 Null 。其实这也反映了 null 的语义,它是一个空指针表示对象为空,而 undefined 才表示什么都没有。

根据上面的内容,简单的对 typeof 做一个归纳:

typeof只能检测基本数据类型,对于null还有一个Bug。

然而在实际写代码的时候,使用 typeof 时需要养成一个好的习惯。比如,使用 typeof 一个较好的习惯是 写一个多种状态的函数

function f (x) {     if (typeof x == "function") {         ... // 当x是一个函数时,做些什么...     } else {         ... // 其它状态     } }

这样使用较为合理,但不建议像下面这样使用 typeof :

// 检测是否存在全局变量jQuery if (typeof(jQuery) !== 'undefined') ...

话峰再回转一下,前面使用 typeof 对一个数组做检测的时候也返回 object 。特别是下面的代码,更易让人迷惑:

typeof new Boolean(true) === 'object'; typeof new Number(1) ==== 'object'; typeof new String("abc") === 'object';

一般情况都不建议这样使用。那么在JavaScrit中,可以通过创建一个函数,并且通过一些正则表达式,让这个函数实现一个改进版本的 typeof 。如下所示:

function toType (obj) {   return ({}).toString.call(obj).match(//s([a-zA-Z]+)/)[1].toLowerCase(); }

来做一个测试:

toType({name: "大漠"}); // => "object" toType(["W3cplus","大漠"]); // => "array" (function() {console.log(toType(arguments))})(); // => arguments toType(new ReferenceError); // => "error" toType(new Date); // => "date" toType(/a-z/); // => "regexp" toType(Math); // => "math" toType(JSON); // => "json" toType(new Number(4)); // => "number" toType(new String("abc")); // => "string" toType(new Boolean(true)); // => "boolean" toType(function foo() {console.log("Test")}); // =>"function"

JavaScrit的变量:如何检测变量类型

将上面的 toType 函数换回成 typeof ,在Chrome重新跑一回,得到的效果将完全不同:

typeof {name: "大漠"}; // => "object" typeof ["W3cplus","大漠"]; // => "object" (function() {console.log(typeof arguments)})(); // => object typeof new ReferenceError; // => "object" typeof new Date; // => "object" typeof /a-z/; // => "object" typeof Math; // => "object" typeof JSON; // => "object" typeof new Number(4); // => "object" typeof new String("abc"); // => "object" typeof new Boolean(true); // => "object" typeof function foo() {console.log("Test")}; // => "function"

JavaScrit的变量:如何检测变量类型

instanceof

instanceof 操作符用于 检测某个对象的原型链是否包含某个构造函数的 prototype 属性 。例如:

// 定义构造函数 function C(){}  function D(){}   var o = new C();  // true,因为 Object.getPrototypeOf(o) === C.prototype o instanceof C;   // false,因为 D.prototype不在o的原型链上 o instanceof D;

o 对象的原型链上有很多对象(成为隐式原型),比如 o.__proto__o.__proto__.__proto__ 等等。因为 Object.getPrototypeOf(o) === C.prototype 所以返回的是 true ,而 D.prototype 不在 o 的原型链上,所以返回的是 false

需要注意的是,如果表达式 o instanceof C 返回 true ,则并不意味着该表达式会永远返回 ture ,因为 C.prototype 属性的值有可能会改变,改变之后的值很有可能不存在于 o 的原型链上,这时原表达式的值就会成为 false 。另外一种情况下,原表达式的值也会改变,就是改变对象 o 的原型链的情况,虽然在目前的ES规范中,我们只能读取对象的原型而不能改变它,但借助于非标准的 __proto__ 魔法属性,是可以实现的。比如执行 o.__proto__ = {} 之后, o instanceof C 就会返回 false 了。

instanceof 是通过原型链来检查类型的。所谓的“类型”是一个构造函数。例如:

// 比如直接原型关系 function Animal () {}; var a =  new Animal(); a instanceof Animal; // => true  // 原型链上的间接原型 function Cat() {}; Cat.prototype = new Animal; var b = new Cat(); b instanceof Animal; // =>

instanceof 除了适用于任何 object 的类型检查之外,也可以用来检测内置兑现,比如: ArrayRegExpObjectFunction

[1, 2, 3] instanceof Array // true /abc/ instanceof RegExp // true ({}) instanceof Object // true (function(){}) instanceof Function // true

instanceof 对基本数据类型检测不起作用,主要是因为 基本数据类型没有原型链

3 instanceof Number // false true instanceof Boolean // false 'abc' instanceof String // false null instanceof XXX  // always false undefined instanceof XXX  // always false

但我们可以这样使用:

new Number(3) instanceof Number // true new Boolean(true) instanceof Boolean // true new String('abc') instanceof String // true

不过这个时候,都知道数据类型了,再使用 instanceof 来做检测就毫无意义了。

简单总结一下:

instanceof 适用于检测对象,它是基于原型链运作的。

constructor

constructor 属性返回一个指向创建了该对象原型的函数引用。需要注意的是, 该属性的值是那个函数本身。 如:

function Animal () {}; var a = new Animal; a.constructor === Animal; // => true

constructor 不适合用来判断变量类型。首先因为它是一个属性,所以非常容易伪造:

var a = new Animal; a.constructor === Array; a.constructor === Animal; // => false

另外 constructor 指向的是最初创建当前对象的函数,是原型链最上层的那个方法:

function Cat () { }; Cat.prototype = new Animal; function BadCat () { }; BadCat.prototype = new Cat; var a = new BadCat; a.constructor === Animal;  // => true Animal.constructor === Function; // => true

instanceof 类似, constructor 只能用于检测对象,对基本数据类型无能为力。而且因为 constructor 是对象属性,在基本数据类型上调用会抛出 TypeError 异常:

null.constructor; // => TypeError undefined.constructor; // => TypeError

instanceof 不同的是,在访问基本数据类型的属性时,JavaScript会自动调用其构造函数来生成一个对象,如:

(3).constructor === Number // true true.constructor === Boolean // true 'abc'.constructor === String // true // 相当于 (new Number(3)).constructor === Number (new Boolean(true)).constructor === Boolean (new String('abc')).constructor === String

另外,使用 constructor 有两个问题。第一个问题它不会走原型链:

function Animal () {}; function Cat () {}; Cat.prototype = new Animal; Cat.prototype.constructor = Cat; var felix = new Cat; felix.constructor === Cat; // => true felix.constructor === Animal; // => false

第二个问题,就是 nullundefined 使用 constructor 会报异常。

同样对 constructor 做个简单的总结:

constructor 指向的是最初创建者,而且易于伪造,不适合做类型判断。

跨窗口问题

JavaScript是运行在宿主环境下的,而每个宿主环境都会提供一套标准的内置对象,以及宿主对象(如 window , document ),一个新的窗口即是一个新的宿主环境。不同的窗口下的内置对象是不同的实例,拥有不同的内存地址。

instanceofconstructor 都是通过比较两个 Function 是否相等来进行类型判断的。 此时显然会出问题,例如:

var iframe = document.createElement('iframe'); var iWindow = iframe.contentWindow; document.body.appendChild(iframe);  iWindow.Array === Array         // false // 相当于 iWindow.Array === window.Array  // false

因此 iWindow 中的数组 arr 原型链上是没有 window.Array 的。请看:

iWindow.document.write('<script> var arr = [1, 2]</script>'); iWindow.arr instanceof Array            // false iWindow.arr instanceof iWindow.Array    // true

toString

最简单的数据类型检测方法应当算是 toString ,不过其看起来像是一个黑魔法:

Object.prototype.toString.call();

toString 属性定义在 Object.prototype 上,因而所有对象都拥有 toString 方法。默认情况之下,调用 {}.toString() (一个 object ),将会得到 [object object]

我们可以通过 .call() 来改变这种情况(因为它将其参数转换为值类型)。例如,通过使用 .call(/test/i) (正则表达多),这个时候 [object object] 将变成 [object RegExp]

Object.prototype.toString.call([]); // => [object Array] Object.prototype.toString.call({}); // => [object Object] Object.prototype.toString.call(''); // => [object String] Object.prototype.toString.call(new Date()); // => [object Date] Object.prototype.toString.call(1); // => [object Number] Object.prototype.toString.call(function () {}); // => [object Function] Object.prototype.toString.call(/test/i); // => [object RegExp] Object.prototype.toString.call(true); // => [object Boolean] Object.prototype.toString.call(null); // => [object Null] Object.prototype.toString.call(); // => [object Undefined]

不过 toString 也不是十全十美的,因为它无法检测用户自定义类型。主要是因为 Object.prototype 是不知道用户会创造什么类型的,它只能检测ECMA标准中的那些内置类型。

function Animal () {}; Object.prototype.toString.call (Animal); // => [object Function] Object.prototype.toString.call (new Animal); // => [object Object]

Object.prototype.toString 类似, Function.prototype.toString 也有类似功能,不过它的 this 只能是 Function ,其它类型(如基本数据类型)都会抛出异常。

自定义检测数据类型的函数

通过前面的内容介绍,我们可以获知:

  • typeof 只能检测基本数据类型,对于 null 还有Bug;
  • instanceof 适用于检测对象,它是基于原型链运作的;
  • constructor 指向的是最初创建者,而且容易伪造,不适合做类型判断;
  • toString 适用于ECMA内置JavaScript类型(包括基本数据类型和内置对象)的类型判断;
  • 基于引用判等的类型检查都有跨窗口问题,比如 instanceofconstructor

总之,如果你要判断的是基本数据类型或JavaScript内置对象,使用toString; 如果要判断的是自定义类型,请使用instanceof。

其实,为了便于使用,可以在 toString 的基础上封闭一个函数。比如 @toddmotto 写的 axis.js :

(function (root, factory) {   if (typeof define === 'function' && define.amd) {     define([], factory);   } else if (typeof exports === 'object') {     module.exports = factory();   } else {     root.axis = factory();   } }(this, function () {    'use strict';    var axis = {};    var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');    function type() {     return Object.prototype.toString.call(this).slice(8, -1);   }    for (var i = types.length; i--;) {     axis['is' + types[i]] = (function (self) {       return function (elem) {         return type.call(elem) === self;       };     })(types[i]);   }    return axis;  }));

有了这个函数,咱们只需要像下面这样使用,就可以检测数据类型:

axis.isArray([]); // true axis.isObject({}); // true axis.isString(''); // true axis.isDate(new Date()); // true axis.isRegExp(/test/i); // true axis.isFunction(function () {}); // true axis.isBoolean(true); // true axis.isNumber(1); // true axis.isNull(null); // true axis.isUndefined(); // true

总结

每个人都希望有一个完美的解决方案。而事实上,检查数据类型有许多有效的方法,只不过这些方法都各有其利弊,从而印证了那句老话,没有最好的,只有更适合的。同样,对于数据类型检查我们需要针对具体情况和项目需求,采用更为适合的方法。为了能让大家更好的理解,为项目做出最明智的决定,从而写出最好的代码。为了方便起见,下面的表格针对 typeofinstanceofconstructortoString 做了一个简单的总结:

typeof instanceof constructor toString
避免字符串比较 No Yes Yes No
常用的 Yes Yes No No
检查自定义类 No Yes Yes No
直接检查null No No No Yes
直接检查undefined Yes No No Yes
跨窗口工作 Yes No No Yes
  • 其他方法包括 Duck Testing (假设基于特殊的类型),具体的方法比如有 Array.isArrayNumber.isNaN 和类似于 Object.prototype.isPrototypeOf 这样的比较方法。当然可能还有一些我忘记了的方法。
  • ES6新增了一种数据类型 symbol 。同时规范是提供了类似 Number.isInteger 方法来检测。
  • ({}).toString.call({}) 这是一个缩写版本。在一些浏览器中可以通过 window.toString.call 或者 toString.call 调用,但它们的结果可能会有所不同。
  • 每个 window/frame 都有其独立的内置对象。有关于这方面的详细介绍可以 阅读这篇文章 。
  • DOM Element的类型检测可以 参见这篇文章 。

最后有关于JavaScript中数据类型检查的方法,我们总结为一句口诀: 如果你要判断的是基本数据类型或JavaScript内置对象,使用 toString ; 如果要判断的时自定义类型,请使用 instanceof

参考文档

  • 如何检查JavaScript变量类型?
  • Checking Types in Javascript
  • Understanding JavaScript types and reliable type checking
  • Fixing the JavaScript typeof operator
  • Comparing Type Checks in JavaScript
JavaScrit的变量:如何检测变量类型

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。

原文  http://www.w3cplus.com/javascript/comparing-type-checks-in-JavaScript.html
正文到此结束
Loading...