19

如果你没有注意过这个问题,那么这个标题应该会让你感到困惑,判断数据类型这么基础的问题能有什么坑呢?

少年,你不能太天真了,我们朝夕面对的这门语言,可是JavaScript呀,任何你觉得已经习以为常的东西都可能瞬间转化成一个大坑,令人百思不得其解。

但是正是因为同样的原因,我们可以在学习和使用JavaScript这门语言的时候提出和讨论一些这门语言独有的,十分有趣的问题。比如我们今天要讨论的,在JavaScript当中如何判断一个数组是数组。

JavaScript有五种方法可以确定一个值到底是什么类型,分别是typeof运算符,constructor法,instanceof运算符,Object.prototype.toString方法以及Array.isArray法.

1.用typeof运算符来判断

typeof是javascript原生提供的判断数据类型的运算符,它会返回一个表示参数的数据类型的字符串,例如:

const s = 'hello';
console.log(typeof(s))//String

以下是我在MDN的文档中找到的一张包含typeof运算法的针对不同参数的输出结果的表格:

typeof运算符返回值一览表

从这张表格可以看出,数组被归到了Any other object当中,所以typeof返回的结果应该是Object,并没有办法区分数组,对象,null等原型链上都有Object的数据类型。

const a = null;
const b = {};
const c= [];
console.log(typeof(a)); //Object
console.log(typeof(b)); //Object
console.log(typeof(c)); //Object

运行上面的代码就会发现,在参数为数组,对象或者null时,typeof返回的结果都是object,可以使用这种方法并不能识别出数组,因此,在JavaScript项目中用typeof来判断一个位置类型的数据是否为数组,是非常不靠谱的。

2.用instanceof判断

既然typeof无法用于判断数组是否为数组,那么用instance运算符来判断是否可行呢?要回答这个问题,我们首先得了解instanceof运算法是干嘛用的。

instanceof运算符可以用来判断某个构造函数的prototype属性所指向的對象是否存在于另外一个要检测对象的原型链上。在使用的时候语法如下:

object instanceof constructor

用我的理解来说,就是要判断一个Object是不是数组(这里不是口误,在JavaScript当中,数组实际上也是一种对象),如果这个Object的原型链上能够找到Array构造函数的话,那么这个Object应该及就是一个数组,如果这个Object的原型链上只能找到Object构造函数的话,那么它就不是一个数组。

const a = [];
const b = {};
console.log(a instanceof Array);//true
console.log(a instanceof Object);//true,在数组的原型链上也能找到Object构造函数
console.log(b instanceof Array);//false

由上面的几行代码可以看出,使用instanceof运算符可以分辨数组和对象,可以判断数组是数组。

3.用constructor判断

实例化的数组拥有一个constructor属性,这个属性指向生成这个数组的方法。

const a = [];
console.log(a.constructor);//function Array(){ [native code] }

以上的代码说明,数组是有一个叫Array的函数实例化的。
如果被判断的对象是其他的数据类型的话,结果如下:

const o = {};
console.log(o.constructor);//function Object(){ [native code] }
const r = /^[0-9]$/;
console.log(r.constructor);//function RegExp() { [native code] }
const n = null;
console.log(n.constructor);//报错

看到这里,你可能会觉得这也是一种靠谱的判断数组的方法,我们可以用以下的方式来判断:

const a = [];
console.log(a.constructor == Array);//true

但是,很遗憾的通知你,constructor属性是可以改写的,如果你一不小心作死改了constructor属性的话,那么使用这种方法就无法判断出数组的真是身份了,写到这里,我不禁想起了无间道的那段经典对白,梁朝伟:“对不起,我是警察。”刘德华:“谁知道呢?”。

//定义一个数组
const a = [];
//作死将constructor属性改成了别的
a.contrtuctor = Object;
console.log(a.constructor == Array);//false (哭脸)
console.log(a.constructor == Object);//true (哭脸)
console.log(a instanceof Array);//true (instanceof火眼金睛)

可以看出,constructor属性被修改之后,就无法用这个方法判断数组是数组了,除非你能保证不会发生constructor属性被改写的情况,否则用这种方法来判断数组也是不靠谱的。

4.用Object的toString方法判断

另一个行之有效的方法就是使用Object.prototype.toString方法来判断,每一个继承自Object的对象都拥有toString的方法。

如果一个对象的toString方法没有被重写过的话,那么toString方法将会返回"[object type]",其中的type代表的是对象的类型,根据type的值,我们就可以判断这个疑似数组的对象到底是不是数组了。

你可能会纠结,为什么不是直接调用数组,或则字符串自己的的toString方法呢?我们试一试就知道了。

const a = ['Hello','Howard'];
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
a.toString();//"Hello,Howard"
b.toString();//"[object Object]"
c.toString();//"Hello,Howard"

从上面的代码可以看出,除了对象之外,其他的数据类型的toString返回的都是内容的字符创,只有对象的toString方法会返回对象的类型。所以要判断除了对象之外的数据的数据类型,我们需要“借用”对象的toString方法,所以我们需要使用call或者apply方法来改变toString方法的执行上下文。

const a = ['Hello','Howard'];
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
Object.prototype.toString.call(a);//"[object Array]"
Object.prototype.toString.call(b);//"[object Object]"
Object.prototype.toString.call(c);//"[object String]"

使用apply方法也能达到同样的效果:

const a = ['Hello','Howard'];
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
Object.prototype.toString.apply(a);//"[object Array]"
Object.prototype.toString.apply(b);//"[object Object]"
Object.prototype.toString.apply(c);//"[object String]"

总结一下,我们就可以用写一个方法来判断数组是否为数组:

const isArray = (something)=>{
    return Object.prototype.toString.call(something) === '[object Array]';
}

cosnt a = [];
const b = {};
isArray(a);//true
isArray(b);//false

但是,如果你非要在创建这个方法之前这么来一下,改变了Object原型链上的toString方法,那我真心帮不了你了...

//重写了toString方法
Object.prototype.toString = () => {
    alert('你吃过了么?');
}
//调用String方法
const a = [];
Object.prototype.toString.call(a);//弹框问你吃过饭没有

当然了,只有在浏览器当中才能看到alert弹框,这个我就不解释了。

5.用Array对象的isArray方法判断

为什么把这种方法放在最后讲呢?因为它是我目前遇到过的最靠谱的判断数组的方法了,当参数为数组的时候,isArray方法返回true,当参数不为数组的时候,isArray方法返回false。

const a = [];
const b = {};
Array.isArray(a);//true
Array.isArray(b);//false

我试着在调用这个方法之前重写了Object.prototype.toString方法:

Object.prototype.toString = ()=>{
    console.log('Hello Howard');
}
const a = [];
Array.isArray(a);//true

并不影响判断的结果。
我又试着修改了constructor对象:

const a = [];
const b = {};
a.constructor = b.constructor;
Array.isArray(a);//true

OK,还是不影响判断的结果。

可见,它与instance运算符判断的方法以及Object.prototype.toString法并不相同,一些列的修改并没有影响到判断的结果。

你可以放心大胆的使用Array.isArray去判断一个对象是不是数组。
除非你不小心重写了Array.isArray方法本身。。


重要补充:有读者朋友在评论中提醒我,Array.isArray是ES5标准中增加的方法,部分比较老的浏览器可能会有兼容问题,所以为了增强健壮性,建议还是给Array.isArray方法进行判断,增强兼容性,重新封装的方法如下:

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

作者:方浩,转载请注明出处!!!
文章源链接:https://segmentfault.com/a/11...

我的微信公众号:webcoding ,欢迎扫码关注
图片描述

20 条评论
zhaopeifei · 2016年08月04日

我认为,用原型链的方式检查应该也是很稳妥的吧。JavaScript是基于对象的,如果一个对象是数组,那么它的原型链中就必然有Array.prototype。有一种特殊情况是,我们新建了一种基于数组的对象,这种情况下也会认为是数组,那么如果不希望这种事情发生的话,可以用Object.getPrototypeOf(a) === Array.prototype。所以,使用原型/原型链本身就是决定对象是否是数组的原因,所以一般出现错误的(然而,各个浏览器的Array.prototype可能不一样呀)。而constructor、toString方法都是产生数组对象的副产品罢了,用来判断自然不合适。出于以上原因,便有了Array.isArray。但是我更想知道的是Array.isArray到底干了什么呢?新手,求轻喷~

+1 回复

nevar · 2016年08月05日

isArray是es5的方法 所以某些情况下 为了兼容老的浏览器 并不会使用isArray的方法 会使用toString

回复

jay_m_hu · 2016年08月06日

cosnt a = [];
const b = [];
isArray(a);//true
isArray(b);//false

这段有问题哈。

回复

0

第一个 "cosnt" 写错了

全真教丿尹志平 · 3月19日
0

收到

jay_m_hu · 3月25日
webFunc 作者 · 2016年08月07日

感谢指出,已经修改。^ ^

回复

webFunc 作者 · 2016年08月07日

感谢您的提醒,我上MDN查阅了一下,确实如此,我已经在文章底部增加了一段Polifill,再次感谢。

回复

webFunc 作者 · 2016年08月07日

如果不作死修改原型链,比如不小心继承了一个其他非数组类型的对象的原型链的话,查找原型链的方法应该是比较稳妥的,不过大部分情况还是难防队友,嘿嘿。Array.isArray()方法到底做了什么,我找了很久也没有找到,如果您有进一步发现,恳求分享。在下面这篇文章中,也仅仅说在标准中加这个方法是为了解决javascript中识别数组类型不准确的问题。http://web.mit.edu/jwalden/ww...
感谢您能看完^ ^

回复

darkbaby123 · 2016年08月09日

Array.isArray 作为 ES 标准应该是现在最推荐的方法。浏览器不支持的话加个 Polyfill 即可。可以看看 babel-polyfill 和 core-js

回复

javascriptuser · 2016年08月10日

我还以为要说如何分辨类数组和数组。。。

回复

webFunc 作者 · 2016年08月12日

根据我的理解,类数组是一个具有length属性的对象,那么区分类数组和数组本质上也是判断待确认的这个对象是不是数组,是这样么?

回复

javascriptuser · 2016年08月12日

JavaScript中,数组这种数据结构不但拥有特殊的属性比如length,并且也会随着属性变化而自动发生指定的操作.伪数组则不会,或者说不会自动而是要注册相关事件.@darkbaby123 说的挺不错,也可以参考jquery中的工具函数isArray() 的实现,贴一下代码(v=1.12.2):
1) isArray

isArray: Array.isArray || function( obj ) {
    return jQuery.type( obj ) === "array";
}

2) jQuery.type()

type: function( obj ) {
    if ( obj == null ) {
        return obj + "";
    }
            // jquery做了如下转换:
            //    class2type[ "[object " + name + "]" ] = name.toLowerCase();
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[ toString.call( obj ) ] || "object" :
        typeof obj;
}

更加详细的,可以参考jquery源码:
http://cdn.bootcss.com/jquery...

回复

萌萌哒野怪 · 2016年08月13日

可以用鸭式辨型的方法, function isArray(arr){ return !!arr.sort}

回复

0

这种方式是利用数组有sort方法来判断的,但是不够严谨,比如

var obj = {
sort () {}
}

这个时候就把他判断成数组了,但是肯定是不对的

谦龙 · 2017年07月04日
0

@谦龙 恩你说得对,去年刚学js还是太年轻了

萌萌哒野怪 · 2017年07月06日
Levon · 2016年08月15日

isArray是es5中就已经有的方法,不是es2015(es6)哈。。。

回复

webFunc 作者 · 2016年08月16日

嗯,感谢指出,已经在文中修改。

回复

Hayley · 2017年09月14日

const a = [];
//作死将constructor属性改成了别的
a.contrtuctor = Object;
console.log(a.constructor == Array);//false (哭脸)
console.log(a.constructor == Object);//true (哭脸)
console.log(a instanceof Array);//true (instanceof火眼金睛)
我在浏览器验证的时候显示一下结果,说明修改了constructor其实应该是没有影响用constructor判断的结果的吧
true
false
true

回复

codevvvv9 · 2017年12月25日

typeof Array typeof String
结果都是function啊

回复

0
typeof new Array()  // "object"
脱缰的哈士奇 · 7月9日
载入中...