
数组字面量与 new Array() 创建的实例都继承自 Array.prototype
使用 [] 或 new Array(1, 2, 3) 创建的对象,都是 Array 构造函数的直接产物。它们的 __proto__ 指向 Array.prototype,再向上指向 Object.prototype,最终到达 null。这条原型链完整且符合标准。
const arr = [];中arr.__proto__ === Array.prototype成立const arr2 = new Array('a', 'b');同样满足上述关系- 因此,
map、filter、push等原生方法均可正常调用,同时Array.isArray(arr)也会正确返回true
手动设置 __proto__ 或使用 Object.create() 不会生成真正的数组
如果说刚才那种是“正规军”,那下面这种做法就是“杂牌军”:仅仅将某个对象的原型设为 Array.prototype,但缺乏内部机制支撑(比如特殊的索引属性描述符、length 的自动响应逻辑),这样的对象在规范意义上根本不是数组。
const fakeArr = Object.create(Array.prototype); fakeArr[0] = 'x';→ 猜猜fakeArr.length的值是多少?仍然是0,因为它没有自动更新的能力。- 虽然
fakeArr.push('y')可以执行(毕竟继承了push方法),但行为可能完全走样,比如length不对、索引混乱。 Array.isArray(fakeArr)的结果为false——该方法检查的是对象是否由Array构造函数创建,仅通过原型链继承并不符合条件。
类数组对象(array-like object)不继承自 Array.prototype,但可通过借用方法使用数组能力
实际开发中还常见一类“仿冒品”:arguments、NodeList、{0: 'a', 1: 'b', length: 2} 等。它们拥有数字索引和 length 属性,看起来很像数组,但原型是 Object.prototype,与 Array.prototype 无关。
- 直接调用
forEach、slice会报错,除非显式借用方法。 - 传统做法:
Array.prototype.slice.call(obj);现代方案:[...obj]展开运算符或Array.from(obj),都能将其转换为真正的数组。 - 注意
Array.from()内部会检查obj[Symbol.isConcatSpreadable]和obj.length属性,并基于此创建一个全新的数组实例,确保原型链正确。
扩展数组时需注意原型链与构造器一致性
如果想自定义一个继承数组的类(比如 class MyArray extends Array),只要遵循 ES6 的类继承规则,子类实例依旧是真正的数组:Array.isArray() 返回 true,push、length 等行为保持原生语义。
- ES6 的类继承机制会自动处理原型链:子类实例的
__proto__指向MyArray.prototype,而MyArray.prototype.__proto__指向Array.prototype。 - 但关键点在于:构造器内必须调用
super()以初始化数组内部状态,否则length等属性可能出现异常。 - 此外,切勿随意修改
Array.prototype——这会影响到全局所有数组实例,并且在严格模式下会直接抛出错误。
总结一句话:创建方式决定数组身份,原型链决定其能力。想要获得真正的数组,请始终使用 [] 或 new Array();遇到类数组对象时,利用 Array.from() 轻松转换;切勿试图通过手动修改原型来伪造数组——那样只会让你在调试过程中陷入困惑。
