js如何检测变量

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

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

1:type of(操作符返回的是字符串,它的返回值如下)

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


  typeof ["w3cplus","大漠"];
    typeof new Date();
    typeof new String("w3cplus");
    typeof new function (){};
    typeof /test/i;  返回的都是object

另外对于Null,typeof检测返回的值也是一个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 对一个数组做检测的时候也返回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"

2: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的类型检查之外,也可以用来检测内置兑现,比如:Array、RegExp、Object、Function:

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

instanceof对基本数据类型检测不起作用,主要是因为基本数据类型没有原型
但是还可以用的:如下:

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

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

简单总结一下

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

3: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会自动调用其构造函数来生成一个对象,如:

.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

就是null和undefined使用constructor会报异常

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

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

跨窗口问题

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

而instanceof和constructor都是通过比较两个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的

toString

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

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类型(包括基本数据类型和内置对象)的类型判断;
基于引用判等的类型检查都有跨窗口问题,比如instanceof和constructor。

总之,如果你要判断的是基本数据类型或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

总结

    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

我们总结为一句口诀:如果你要判断的是基本数据类型或JavaScript内置对象,使用toString; 如果要判断的时自定义类型,请使用instanceof。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 1.通过typeof可以判断处几种基本数据类型Boolean,number,string,null,undefin...
    舟渔行舟阅读 623评论 0 1
  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,092评论 0 6
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,093评论 0 21
  • 【大楚】的情书 大楚宝贝, 前段时间我们读了个物理故事绘本,故事本身你不感兴趣,却对里面提到的物理变化和化学变...
    浮沉浮沉阅读 118评论 0 0
  • 最放松的周末,生物钟就是这么强大,6:00前自然醒了,原本计划晨跑的,但想到晩上20:10-21:00要去游泳,而...
    好心情2816阅读 163评论 1 1