如何用Proxy或Object.defineProperty实现二维数组的行列双向读写封装?
二维数组双向维度读写包装器实现方案
问题描述
需要为二维数组创建一个包装器,支持以下操作且所有读写操作同步到原数组(通过引用同步):
- 读写单行(原生数组默认支持)
- 读写单列
- 从列维度读写单个单元格
期望效果示例:
function myFunction() { var myArray = [['a1','b1'],['a2','b2']]; var ds = new DataStructure(myArray); console.log(ds) // -> [['a1','b1'],['a2','b2']] console.log(ds[0]) // -> ['a1','b1'] console.log(ds[1]) // -> ['a2','b2'] console.log(ds.cols[0]) // -> ['a1','a2'] console.log(ds.cols[1]) // -> ['b1','b2'] ds.cols[0] = ['A1','A2'] console.log(ds.cols[0]) // -> ['A1','A2'] ds.cols[1][1] = 'Q2' console.log(ds.cols[1]) // -> ['b1','Q2'] console.log(ds) // -> [['A1','b1'],['A2','Q2']] console.log(myArray) // -> [['A1','b1'],['A2','Q2']] }
之前尝试用Proxy()和Object.defineProperty()实现,但访问列的深层元素(如ds.cols[0][0])时无法同步修改原数组,需要解决这个问题。
解决方案:嵌套Proxy实现双向同步
核心思路是用多层Proxy分别处理:
- 包装器实例的行访问与
cols属性获取 cols对象的列获取与赋值- 列数组的元素读写,确保修改列元素时同步更新原数组的对应位置
完整实现代码
class DataStructure { constructor(originalArray) { this.original = originalArray; // 列代理:处理cols的列获取与赋值 const colsProxy = new Proxy({}, { get: (_, colIndex) => { // 返回列的代理数组,实现元素读写同步 return new Proxy([], { get: (_, rowIndex) => { return this.original[rowIndex][colIndex]; }, set: (_, rowIndex, value) => { this.original[rowIndex][colIndex] = value; return true; }, // 支持列数组的长度获取 getOwnPropertyDescriptor: (_, prop) => { if (prop === 'length') { return { value: this.original.length }; } return Object.getOwnPropertyDescriptor([], prop); }, // 遍历列元素 ownKeys: () => { return [...Array(this.original.length).keys(), 'length']; } }); }, set: (_, colIndex, newColArray) => { // 赋值整列时,同步更新原数组的每一行对应列 for (let rowIndex = 0; rowIndex < this.original.length; rowIndex++) { this.original[rowIndex][colIndex] = rowIndex < newColArray.length ? newColArray[rowIndex] : undefined; } return true; } }); // 实例代理:处理行访问与cols属性 return new Proxy(this, { get: (target, prop) => { if (prop === 'cols') { return colsProxy; } // 处理行索引访问 if (!isNaN(Number(prop))) { return target.original[prop]; } // 其他属性正常返回 return Reflect.get(target, prop); }, set: (target, prop, value) => { // 处理行赋值 if (!isNaN(Number(prop))) { target.original[prop] = value; return true; } return Reflect.set(target, prop, value); }, // 让实例能被正常遍历和转换为数组 getOwnPropertyDescriptor: (target, prop) => { if (!isNaN(Number(prop)) && Number(prop) < target.original.length) { return Object.getOwnPropertyDescriptor(target.original, prop); } return Reflect.getOwnPropertyDescriptor(target, prop); }, ownKeys: (target) => { return [...Array(target.original.length).keys(), ...Reflect.ownKeys(target)]; } }); } }
代码说明
实例Proxy:
- 拦截行索引的读写,直接映射到原数组的对应行
- 拦截
cols属性的获取,返回列代理对象 - 实现数组的遍历和长度相关特性,确保
console.log(ds)能正常输出数组结构
列Proxy:
- 拦截列索引的获取,返回一个代理数组
- 拦截列的赋值操作,将新数组的每个元素同步到原数组的对应行的列位置
列元素Proxy:
- 对每个列的数组做代理,拦截元素的读写操作,直接映射到原数组的
original[rowIndex][colIndex]位置 - 实现
length属性和遍历特性,让列数组表现得像普通数组
- 对每个列的数组做代理,拦截元素的读写操作,直接映射到原数组的
测试验证
运行修正后的测试函数,所有操作都会同步到原数组,包括列的深层元素修改。
内容的提问来源于stack exchange,提问作者Christian Schäfer
相关产品推荐
相关产品推荐

