如何在JavaScript中自定义支持数字索引访问的类数组对象
实现方案
JavaScript中对象的方括号索引访问本质是字符串属性访问(collection[0]等价于collection['0']),有两种主流实现方式可以满足你的需求:
方案1:ES6 Proxy 实现(推荐,无需手动同步属性)
这是目前最简洁的实现方式,通过代理拦截所有属性访问,直接映射到内部数组的对应属性:
function MyCollection(array) { this.array = array || []; // 构造函数直接返回代理实例,外部使用时无感知 return new Proxy(this, { get(target, prop) { // 优先返回实例自身定义的属性/方法 if (Reflect.has(target, prop)) { return Reflect.get(target, prop); } // 数字索引或类数字字符串属性,访问内部数组 if (!isNaN(Number(prop))) { return target.array[Number(prop)]; } // 其他属性也可以透传给内部数组,按需调整 return target.array[prop]; }, set(target, prop, value) { // 支持通过索引直接赋值:collection[2] = 'c' if (!isNaN(Number(prop))) { target.array[Number(prop)] = value; return true; } // 其他属性赋值走默认逻辑 return Reflect.set(target, prop, value); } }); } // 你原有原型方法不需要修改,直接保留即可 MyCollection.prototype[Symbol.iterator] = function () { const array = this.array; let position = -1; let isDone = false; return { next: () => { position++; isDone = position >= array.length; return { done: isDone, value: array[position] }; } }; }; Object.defineProperty(MyCollection.prototype, "length", { get: function () { return this.array.length; } }); MyCollection.prototype.append = function (item) { this.array.push(item); };
你给出的测试代码可以直接运行,输出结果完全符合预期。
方案2:手动同步属性(jQuery采用的实现方式,兼容低版本ES)
jQuery的实现逻辑非常直接:每次修改集合元素时,把元素同步挂载到实例的数字属性上,你没找到相关逻辑是因为这部分代码分散在所有修改集合的方法里:
function MyCollection(array) { this.array = array || []; // 初始化时同步所有元素到实例属性 this._syncProps(); } // 新增同步方法 MyCollection.prototype._syncProps = function() { // 先清理旧的数字属性 Object.keys(this).forEach(key => { if (!isNaN(Number(key))) delete this[key]; }); // 同步新的元素 this.array.forEach((item, index) => { this[index] = item; }); }; // 原有修改数组的方法都要调用_syncProps完成同步 MyCollection.prototype.append = function (item) { this.array.push(item); this._syncProps(); }; // 其他原型方法(迭代器、length属性)和之前一致,不需要修改
这种方案的缺点是如果要支持删除、插入、排序等修改操作,每个方法都要手动调用同步方法,维护成本更高,优点是兼容不支持Proxy的低版本ES环境。
内容的提问来源于stack exchange,提问作者ygoe
相关产品推荐
相关产品推荐

