判断一个变量是不是数组,听起来是个再简单不过的需求,但在ES5之前的老浏览器环境里,这件事远没有看上去那么容易。原生Array.isArray方法是ES5才正式引入的,IE8及更早的浏览器、以及一些老旧的嵌入式Webkit内核都不支持它。jQuery作为当年统治前端的类库,很早就封装了jQuery.isArray来解决这个问题,理解它的实现思路,对我们维护老项目、编写兼容性代码非常有帮助。

为什么typeof和instanceof都不能可靠判断数组
先看最直觉的写法:typeof arr。可惜它行不通。对数组执行typeof运算,返回的结果是字符串"object",和普通对象、null、甚至Date对象得到的结果完全一样。这是因为JavaScript在底层对类型的划分本来就比较粗,typeof只能区分出object、function、string、number、boolean、undefined这几类,数组并没有独立的位置。所以在兼容性代码里,typeof arr === "object"根本无法证明arr是数组,它只能证明arr是个引用类型。
再来看instanceof。在单页面、单全局环境的应用里,arr instanceof Array通常能正常工作,但它的前提是arr确实是由当前页面全局环境中的Array构造函数创建的。一旦涉及iframe或者弹出窗口,问题就来了:父窗口传入一个iframe里的数组,这个数组的原型链指向的是iframe自己的Array.prototype,而不是父窗口的Array.prototype,instanceof判断就会返回false。实际上数组明明是个货真价实的数组,却被误判成了普通对象,这类bug在跨frame通信的老代码里非常典型。
还有一种曾经流行的写法是检查constructor属性:arr.constructor === Array。它同样脆弱,一是constructor属性可以被随意改写,二是跨frame时也会遇到和instanceof一样的问题。所以说,想要一个真正可靠的数组判断方案,必须绕开构造函数和原型链,找一个对象自身不可伪造的特征。
jQuery.isArray的实现原理剖析
jQuery给出的答案,核心思路是利用Object.prototype.toString。JavaScript规范规定,对这个方法调用call,并传入任意对象,它会返回一个形如"[object Array]"、"[object Object]"、"[object Function]"的字符串,其中中间那段就是对象内部的Class属性。这个Class是引擎内部标记,不受用户代码干扰,也无法通过改写constructor或原型来伪造,因此是判断数组的黄金标准。
翻看jQuery早期源码,它的实现大致如下:
var toString = Object.prototype.toString;
var class2type = {};
// 初始化类型映射表
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
class2type["[object " + name + "]"] = name.toLowerCase();
});
jQuery.extend({
isArray: function(obj) {
// ES5环境优先使用原生方法
if (Array.isArray) {
return Array.isArray(obj);
}
return toString.call(obj) === "[object Array]";
},
type: function(obj) {
if (obj == null) {
return String(obj);
}
return typeof obj === "object" || typeof obj === "function"
? class2type[toString.call(obj)] || "object"
: typeof obj;
}
});这段代码有两个值得学习的细节。第一,jQuery做了能力检测,如果宿主环境已经支持原生的Array.isArray,就直接用原生方法,性能更好也和规范完全一致;只有在不支持的老环境里才降级到toString方案。第二,除了isArray,jQuery还顺便维护了一张class2type映射表,通过jQuery.type可以一次性判断出数组、日期、正则、函数等各种精确类型,isArray只是这套类型体系中的一个特例。这种设计思路在今天写工具库时依然适用。
顺便提一句,早期的极老版本jQuery(1.3之前)其实还用过一种方法检测,即检查obj.length和obj.push等特征是否存在,这种鸭子判断对类数组对象会误判,后来才全面切换到toString方案,这也从侧面说明了toString方案的可靠性更高。
自己动手封装一个跨浏览器polyfill
理解了原理之后,我们完全可以不依赖jQuery,自己封装一个安全的polyfill,直接挂到Array构造函数上,这样业务代码就可以无顾虑地使用Array.isArray而不必担心老浏览器报错:
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
// 测试各种边界情况
console.log(Array.isArray([])); // true
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray(new Array())); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray('abc')); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray(undefined)); // false
console.log(Array.isArray({ length: 3 })); // false,类数组不算数组
console.log((function() {
return Array.isArray(arguments); // false,arguments也不是数组
})());需要特别注意的是最后两个用例。带length属性的普通对象,以及函数内部的arguments对象,虽然看起来很像数组,但它们的内部Class是Object,所以toString方案会正确地把它们排除掉。如果你判断数组的目的是为了安全地调用push、slice等数组方法,这种严格区分正是你想要的;如果你只是想遍历一组数据,那可能需要的是jQuery.isPlainObject或类数组判断逻辑,两者不要混为一谈。
另外还有一个补充手段:Array.prototype.concat结合call的技巧,或者利用ES5的Object.prototype.hasOwnProperty配合splice特征做双重校验,但这些都属于历史方案,可靠性都不如toString。如今在实际项目中,除非明确要支持IE8,否则优先使用原生Array.isArray即可;需要兼容老环境时,加上上面这段几行代码的polyfill,或者直接引入jQuery.isArray,就能一劳永逸地解决数组判断问题。掌握了这套思路,面对其他类型判断需求时也能举一反三。
jQuery.isArrayArray.isArray数组类型判断修改时间:2026-09-06 19:21:17