更加健壮的 isArray 方法

Published:

在实际项目开发过程中,我们经常会遇到检测一个对象是否是数组的情况,这里会有一些坑,现在来说一说。

javascript中,检测对象类型有下面这么几种方法:

1. typeof

typeof function () {}; // 'function'
typeof 'hello';        // 'string'
typeof 123;            // 'number'
typeof undefined;      // 'undefined'
typeof null;           // 'object'
typeof {};             // 'object'
typeof [];             // 'object'

可以看到,typeof 在检测 Array 类型时无能为力……

2. instanceof

instanceof 作符检测对象的原型链是否指向构造函数的 prototype 对象。于是我们可以这样检测:

[] instanceof Array;    // true

3. constructor属性

[].constructor === Array;    // true

instanceofconstructor 看起来是可以的,但是我们看下面这个例子:

var iframe = document.createElement('iframe');
iframe.name = 'myFrame';
iframe.id = 'myFrame';
document.body.appendChild(iframe);
var frameObj = document.getElementById('myFrame');
var myArray = frameObj.contentWindow.Array;
var arr = new myArray(1, 2, 3);
arr instanceof Array;      // false
arr.constructor === Array; // false

这是由于每个 iframe 都有一套自己的执行环境,跨 iframe 实例化的对象彼此之间是不会共享原型链的。

肿么办,可以看看各大类库的实现:

underscore的实现

_.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) == '[object Array]';
};

lodash的实现

var isArray = nativeIsArray || function(value) {
    return
        value
        &&
        typeof value == 'object'
        &&
        typeof value.length == 'number'
        &&
        toString.call(value) == arrayClass || false;
};

从上面列举的两个类库可以看出,基本的套路就是返回待检测的对象的 toString(),判断结果是否是 [object Array],如果是,则说明此对象是 Array 的实例,否则不是。 原理在此:ECMA-262-3 15.2.4.2。 当 Object.prototype.toString 被调用时,采用如下步骤:

  1. 获取 [[Class]] 内部属性的值
  2. 计算把这三个字符串 '[object ', 第1步的结果, ']' 连在一起的字符串值
  3. 返回第 2 步的结果

因此,不用任何类库的话,检测数组时,我们可以用如下方法:

var isArray = Array.isArray || function (obj) {
    return Object.prototype.toString.call(obj) == '[object Array]';
}