判断一个对象是否为数组的 4 种方法
Category(分类): JavaScript Status: 已整理
在 JavaScript 中,typeof [] 和 typeof {} 都是 "object",所以 typeof 不能直接区分数组和普通对象:
console.log(typeof []) // object
console.log(typeof {}) // object
下面保留原文讨论的四类方法,并补充它们的边界。生产代码通常优先使用 Array.isArray()。
方法一:判断原型
const object = {}
const array = []
console.log(Object.getPrototypeOf(object) === Object.prototype) // true
console.log(Object.getPrototypeOf(array) === Array.prototype) // true
这种方法只适合分析一个由当前 realm 创建、且原型没有被修改的普通数组。对象的 [[Prototype]] 可以被改变,跨 iframe 或其他 realm 的数组也有不同的 Array.prototype:
const object = {}
Object.setPrototypeOf(object, Array.prototype)
// 这只能说明 object 的原型被改成了 Array.prototype,
// 不能说明它真的具有数组的内部槽。
console.log(Object.getPrototypeOf(object) === Array.prototype) // true
console.log(Array.isArray(object)) // false
Object.setPrototypeOf 还可能影响性能,不应为了类型检测修改对象原型。
方法二:判断构造函数或使用 instanceof
const object = {}
const array = []
console.log(array.constructor === Array) // true(未被篡改时)
console.log(array instanceof Array) // true(当前 realm 中)
这种方法容易受两个问题影响:
constructor属性可以被覆盖,不能作为可靠的品牌检查。instanceof Array依赖原型链和当前 realm。来自 iframe 的数组,可能不是当前窗口的Array实例。
// 浏览器中示例:跨 iframe 的数组
const iframe = document.createElement('iframe')
document.body.append(iframe)
const foreignArray = new iframe.contentWindow.Array(1, 2, 3)
console.log(foreignArray instanceof Array) // false
console.log(Array.isArray(foreignArray)) // true
instanceof 可以通过 Symbol.hasInstance 自定义,因此它适合判断“是否属于某个构造器体系”,不适合替代数组品牌检查。
方法三:Object.prototype.toString
const tag = Object.prototype.toString
console.log(tag.call([])) // [object Array]
console.log(tag.call({})) // [object Object]
console.log(tag.call(null)) // [object Null]
直接调用 array.toString() 得到的是数组元素拼接后的字符串,例如 [].toString() 是空字符串,并不是类型标签。因此需要调用 Object.prototype.toString.call(value)。
这个方法比读取 constructor 稳定,也通常能识别跨 realm 的数组;但对象可以通过 Symbol.toStringTag 影响标签,现代代码仍然应优先使用 Array.isArray()。
const fakeArray = {
[Symbol.toStringTag]: 'Array',
}
console.log(Object.prototype.toString.call(fakeArray)) // [object Array]
console.log(Array.isArray(fakeArray)) // false
方法四:Array.isArray()(推荐)
console.log(Array.isArray([])) // true
console.log(Array.isArray({})) // false
console.log(Array.isArray('array')) // false
console.log(Array.isArray({ length: 0 })) // false
Array.isArray() 使用 ECMAScript 的 IsArray 语义进行品牌判断,不依赖当前 realm 的原型、constructor 或 Symbol.toStringTag,因此是判断数组最可靠、最清晰的方式。
const object = {}
const array = []
Object.setPrototypeOf(object, Array.prototype)
Object.getPrototypeOf(object).constructor = Array
console.log(Array.isArray(object)) // false
console.log(Array.isArray(array)) // true
类数组对象不是数组
arguments、NodeList、HTMLCollection 以及手动创建的 { 0: 'a', length: 1 } 可能是可迭代或类数组对象,但它们不是数组:
function inspect() {
console.log(Array.isArray(arguments)) // false
console.log(Array.from(arguments)) // ['a', 'b']
}
inspect('a', 'b')
需要数组方法时,可以使用 Array.from(value) 或展开语法(前提是对象可迭代):
const arrayLike = { 0: 'a', 1: 'b', length: 2 }
const array = Array.from(arrayLike)
console.log(array) // ['a', 'b']
总结
| 方法 | 主要问题 | 建议 |
|---|---|---|
Object.getPrototypeOf(value) === Array.prototype | 原型可修改、跨 realm 失效 | 只适合原理演示 |
value.constructor === Array / instanceof Array | 构造函数可覆盖、instanceof 受原型链和 realm 影响 | 只在明确的当前 realm 场景使用 |
Object.prototype.toString.call(value) | Symbol.toStringTag 可影响标签 | 兼容旧代码时可用 |
Array.isArray(value) | 语义清晰,支持跨 realm | 首选 |
在深拷贝、参数校验、序列化或数组方法封装中,需要区分数组和对象时,优先写 Array.isArray(value)。
原文参考:判断一个对象是否为数组的4种方法
参考资料: