JavaScript扩展继承Array类如何正确支持迭代器等原生特性
问题根源
你遇到的现象不是Firefox 93的专属bug,所有符合ES标准的JS引擎都会输出同样结果,问题出在两点:
- 构造函数的参数传递逻辑错误:你在子类构造器中用
...i收集所有传入参数,当执行new SubArray([1,2,3])时,传入的参数是1个数组,因此收集到的i是[[1,2,3]],展开传给super时等价于调用new Array([1,2,3])。 - 对原生Array构造函数的重载逻辑不熟悉:Array构造函数本身对参数的处理规则是:
- 仅传入1个非负整数参数时,创建指定长度的空位数组
- 传入1个非整数/非数值类型参数、或传入多个参数时,会把所有传入的参数按顺序作为数组的初始元素
所以new Array([1,2,3])本身就会创建一个长度为1、第一个元素是[1,2,3]的数组,和你是否继承Array没有关系。
正确继承Array的实现方案
根据你是否需要自定义构造逻辑,可以选两种写法:
无额外构造逻辑的最简写法
如果不需要在构造函数中添加自定义初始化代码,完全不需要重写constructor,JS默认的构造函数会自动透传所有参数给父类,天然和原生Array行为一致:
class SubArray extends Array { // 此处直接添加自定义方法即可 getFirstItem() { return this[0] } } // 测试 const arr1 = new SubArray(1, 2, 3) console.log(arr1[0]) // 输出1,符合预期 const arr2 = new SubArray(3) console.log(arr2.length) // 输出3,和原生Array创建空位数组的行为完全一致
需要自定义构造逻辑的写法
如果必须在构造函数中添加自己的初始化逻辑,注意两点即可:
- 透传参数时不要修改参数结构,直接把收集到的参数展开传给super即可
- 配置
Symbol.species属性,保证map、filter、slice等返回新数组的实例方法,返回的是你的子类实例而不是原生Array
class SubArray extends Array { constructor(...args) { super(...args) // 自定义初始化逻辑写在这里 this.createTime = Date.now() } // 配置species,保证数组方法返回子类实例 static get [Symbol.species]() { return this } }
使用注意事项
- 传参要符合Array构造函数的规则:如果要得到元素为1、2、3的数组,要么逐个传参
new SubArray(1,2,3),要么把已有数组展开传入new SubArray(...[1,2,3]),不要直接把整个数组作为唯一参数传入——哪怕是原生Array,new Array([1,2,3])也得不到你要的一维数组。 - 更稳妥的已有数组转换方式是用继承来的
from方法:const arr = SubArray.from([1,2,3]),不需要手动展开参数,得到的实例元素顺序、长度都和原数组一致,同时支持迭代器、索引读写、所有原生数组方法,map/filter等方法的返回值也保持子类类型。
const arr = SubArray.from([1,2,3]) console.log(arr[0]) // 1 console.log(arr instanceof SubArray) // true // 验证迭代能力 for (const item of arr) { console.log(item) // 依次输出1、2、3 } // 验证数组方法返回值类型 const doubled = arr.map(i => i * 2) console.log(doubled instanceof SubArray) // true
内容的提问来源于stack exchange,提问作者Abs_0_
相关产品推荐
相关产品推荐

