ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JavaScript数组对象重复多次的完整方案:从浅拷贝到深拷贝实践

JavaScript数组对象重复多次的完整方案:从浅拷贝到深拷贝实践 刚入行那阵子我最怕接到类似“把这块数据复制三份渲染出来”的需求。不是不会写而是每次写完总觉得别扭for循环套push、concat嵌套、ES5时代的apply…代码能跑但怎么看怎么像临时拼凑。直到后来有一次处理日历组件需要把一个长度为7的星期数组重复生成5周、一共35个单元格我才认真把“让一个数组对象重复多次”这件事在JavaScript里彻底磨透。这篇文章就是我基于那次经历把各种实现方案、隐藏陷阱、性能实测和应用场景全部沉淀下来的总结。如果你也经常操作数组对象或者正在写日历、分页、骨架屏这类需要批量复制数据的业务这篇内容应该能让你少走不少弯路。1. 需求拆解搞懂“重复多次”背后的四个隐藏问题很多人看到“让一个数组对象重复多次”这个需求第一反应是“这有什么好讲的循环push不就行了”。但实际接手过这类需求的人都知道真正麻烦的不是“重复”——而是重复之后的“对象关系”。同样是复制有的需求要的是独立对象有的却允许共享引用这两者代码写起来天差地别。1.1 先分清浅拷贝与深拷贝假设你的原数组长这样const baseItems [ { id: 1, name: 张三 }, { id: 2, name: 李四 } ];如果你用Array.from({ length: 3 }).fill(baseItems)得到的结果是同一个数组引用被填了三次。这时候你改result[0][0].nameresult[1][0].name和result[2][0].name会跟着一起变。这就是浅拷贝——它共享的是嵌套对象的内存地址。如果业务要求的是“三份独立数据互不影响”那你就得做深拷贝。深拷贝的实现方式直接决定了你的代码复杂度和性能表现后面我会单独用一整个章节来讲。1.2 重复次数是固定值还是动态算出来的固定值最简单写个3或者5就行。但真实业务里重复次数往往是动态的。比如日历组件要按月份的天数动态计算需要补多少个空白天数这时候重复次数就是变量const daysInMonth 31; const calendarCells Array.from({ length: daysInMonth }, (_, i) i 1);还有些场景更阴险——重复次数本身要和原数组长度挂钩。比如“把2个按钮重复生成直到总数达到10个”这时候就得先算清楚需要重复几次再考虑余数怎么处理。1.3 顺序与层级拼接还是嵌套“重复多次”有两种输出形态第一种是拼接式也就是把数组平铺展开。[A, B, A, B, A, B]重复三次的结果是长度为6的一维数组这是最常见的需求。第二种是嵌套式也就是生成二维数组。[[A, B], [A, B], [A, B]]每个重复单元保留自己的边界适用于分页、分组、卡片列表分块等场景。这两种输出的代码写法完全不同选错方案会绕很大的弯。后面第四节的实现方案里我会把拼接式和嵌套式分别讲清楚。1.4 原数组是否会中途被修改还有一个很多人忽略的关键点重复之后原数组在后续业务流程中会不会被修改。如果原数组后续会被push、splice或者sort那复制出来的结果也会跟着受影响——因为它们共享同一个数组引用。这是并发和状态管理场景下最容易爆雷的地方。我通常接到这个需求的第一件事不是写代码而是先和产品、后端确认这几份数据是“看起来一样但骨子里独立”还是“同一个东西显示多遍”这个确认做完方案基本就定了。如果没确认我默认按深拷贝去处理宁可多一点性能开销也不能让线上数据互相污染。2. 五种主流实现方案详解从易到难附代码拆解这个需求本身不复杂但实现方式五花八门每一种都有自己的适用场景和隐含坑。我按从推荐到冷门的顺序把实际开发中我能想到的方案全部过一遍每个方案都附上可以跑的代码示例和原理说明。2.1 方案一Array.from flatMap我日常最推荐先说结论——现在我做拼接式重复最常用的是Array.from配合Array.prototype.flatMapconst baseList [{ name: 基本款 }, { name: 进阶款 }]; const repeated Array.from({ length: 3 }, () baseList).flat(); // 结果: [{ name: 基本款 }, { name: 进阶款 }, { name: 基本款 }, { name: 进阶款 }, { name: 基本款 }, { name: 进阶款 }]核心逻辑拆开看是两层第一层Array.from({ length: 3 }, () baseList)生成了一个二维数组结构是[baseList, baseList, baseList]也就是三个数组元素每个元素都还是原来的数组引用。第二层.flat()把二维数组拍平成一维。默认只展开一层恰好够用。这种写法的优势在于可读性极强语义一目了然——“生成长度为3的数组每个元素都是baseList再拍平”。后面维护代码的人看到这段不需要查文档就能理解意图。如果不想用flat()也可以用flatMap一步到位const repeated Array.from({ length: 3 }, () baseList).flatMap(item item); // 或者更简洁 const repeated Array.from({ length: 3 }).flatMap(() baseList);注意我这里说的是拼接式重复。如果你要的是嵌套式二维数组那就直接用Array.from({ length: 3 }, () baseList)不加.flat()就够了。2.2 方案二扩展运算符 fill最简短但有坑如果你追求代码最短可以这样写const baseList [1, 2, 3]; const repeated [...Array(3)].flatMap(() baseList); // 结果: [1, 2, 3, 1, 2, 3, 1, 2, 3]这里的原理是Array(3)创建了一个长度为3的稀疏数组里面一个元素都没有。直接对它.flatMap()或者.fill()会得到空结果因为稀疏数组的“空槽”会被数组方法直接跳过。所以要先展开[...Array(3)]变成[undefined, undefined, undefined]然后再操作。你也可以用Array.from替代展开操作const repeated Array.from(Array(3)).flatMap(() baseList);这两种写法的简写方式不同本质逻辑一样。但这里我要提醒一个非常容易踩的坑fill配合map的经典误用。很多新手会这样写const result Array(3).fill(baseList).flat();问题在于fill(baseList)是把同一个数组引用填进每个槽位。虽然你看到的是三个元素但它们指向同一个内存地址。后续你要push或者修改任何一层数组其他两层全部遭殃。这在业务代码里会造成极其隐蔽的数据污染排查起来非常困难。2.3 方案三reduce concat 累加器方案如果你在维护老项目或者团队禁止使用flatMap因为某些旧版浏览器不支持可以用reduceconst baseList [{ id: a }, { id: b }]; const repeated Array.from({ length: 3 }).reduce((acc, _) acc.concat(baseList), []); // 结果: [{ id: a }, { id: b }, { id: a }, { id: b }, { id: a }, { id: b }]这里的原理是初始化一个空数组[]作为累加器每次循环用concat把baseList拼接进去返回新数组覆盖累加器。循环3次就拼接了3份。优点是兼容性极好ES5 时代就能跑。缺点是性能相对较差——concat每次都会创建一个新数组反复分配内存。如果你处理的是只有几十个元素的数组无所谓但如果有上万条数据还这么写浏览器会明显卡顿。2.4 方案四纯循环 push最朴素但最可控有些场景我反而推荐回归最原始的循环function repeatArray(baseList, times) { const result []; for (let i 0; i times; i) { result.push(...baseList); } return result; }用push配合展开运算符把原数组元素逐个加入结果数组重复N次。这个方案最适合的场景是需要在重复过程中对数据做额外处理。比如每个循环给你加一个groupIndex字段或者按批次做不同的标记。因为循环内部你可以任意插入逻辑其他方案则要额外用map。还有一个用splice或push.apply的老写法function repeatArrayOld(baseList, times) { const result []; for (let i 0; i times; i) { Array.prototype.push.apply(result, baseList); } return result; }在 ES6 之前这是最正统的写法用来替代result.concat(baseList)反复拼接的性能问题。现在有展开运算符后基本可以替代这种写法了。2.5 方案五封装成工具函数一劳永逸如果你在一个项目里多次用到这个功能最明智的做法是封装成工具函数放到公共模块里/** * 将数组重复拼接指定次数 * param {Array} arr 原数组 * param {number} times 重复次数 * param {boolean} deepClone 是否需要深拷贝内部对象 * returns {Array} 拼接后的数组 */ function repeatArr(arr, times, deepClone false) { if (!Array.isArray(arr)) throw new TypeError(arr must be an array); if (times 0) return []; const source deepClone ? JSON.parse(JSON.stringify(arr)) : arr; return Array.from({ length: times }, () source).flat(); }这样一个函数就能覆盖80%的场景后续维护也方便。不过JSON.parse(JSON.stringify())做深拷贝有两个致命缺点——不能处理undefined、Function、Symbol也不能处理循环引用。更稳妥的方案我会在第四章详细展开。3. 方案对比与选型建议哪个场景用哪个看完五种方案你可能会晕到底该用哪个这里我整理了一张横向对比表再结合我自己的项目经验给选型建议。3.1 横向参数对比方案代码长度性能表现可读性兼容性要求适用场景Array.from flatMap短优秀极高ES2019现代浏览器项目首选扩展运算符 fill最短良好中等ES2015快速简单场景reduce concat中等较差中等ES5老项目兼容纯循环 push较长优秀高ES2015需中途加逻辑封装工具函数中等优秀高取决于内部实现项目内多次使用从我个人的角度来看在不需要兼容老浏览器的情况下Array.from flatMap和工具函数是最优先的选择。填坑和选型之间尽量选择代码意图明确的方案让下一个接手的人能一眼看出你要干什么。3.2 同业务场景下三分钟确定方案我在开发中总结了一个简单的选择逻辑你可以直接套用项目是现代技术栈React/Vue3 Babel且不要求兼容IE——直接用Array.from flatMap。项目有历史包袱需要兼容低版本浏览器——用reduce concat或者纯循环。需要在重复时修改每个元素的字段比如给复制的数据加上批次号、时间戳——用纯循环在循环体内处理。只需要一维平铺且是嵌套二维也无所谓的泛数据——直接封装一个repeatArr工具函数。这里面最容易范的错误是为了炫技选了最简短的写法结果因为浅拷贝问题导致数据互相污染。所以我一直的态度是在“重复数组对象”这个需求上优先保证数据独立性再考虑代码简洁度。4. 对象引用陷阱与深拷贝实践这一章节是整个方法论的灵魂。很多数组重复导致的线上bug归根结底都是“引用共享”在作祟。我在实际开发中遇到的最离奇的一个bug是后台配置的文案被批量复制到多张卡片展示用户在第一张卡片上改了备注结果所有卡片上的备注一起变了。排查了半天最后定位到是浅拷贝。4.1 fill 和 map 的引用陷阱实测先看一个实际业务里几乎肯定会踩的坑const baseCard { title: 活动卡片, status: 待审核 }; const cards Array.from({ length: 3 }, () baseCard); cards[0].status 已通过;执行结束后cards[1].status和cards[2].status全部变成已通过。因为() baseCard返回的是同一个对象的引用三个数组元素都指向同一个对象。你以为自己生成了三张独立卡片实际上只是把同一张卡片的“遥控器”复制了三份。这种bug不会立即报错而是在用户操作时才露馅排查成本非常高。4.2 JSON序列化深拷贝短平快但有限制最常见的深拷贝做法是const cards Array.from({ length: 3 }, () JSON.parse(JSON.stringify(baseCard)));这段代码能把baseCard里面所有可序列化的属性完整复制一份互相独立。我在小型项目中经常这么干简单粗暴。但它的限制也特别明显var test { a: undefined }序列化后a字段会丢失因为JSON.stringify会忽略值为undefined的属性函数属性会直接消失Symbol作为 key 也会被忽略循环引用对象内部引用自己会直接抛TypeError: Converting circular structure to JSONDate对象会变成字符串RegExp会变成空对象。如果数据里包含上述任何一种类型JSON方案就会静默出错或直接崩溃。我建议只把JSON.parse(JSON.stringify())用在纯数据对象上。4.3 structuredClone现代浏览器的首选深拷贝方案从 Chrome 98 和 Node.js 17 开始我们可以用原生structuredCloneconst cards Array.from({ length: 3 }, () structuredClone(baseCard));structuredClone支持深拷贝大部分内置类型包括Date、RegExp、Map、Set、ArrayBuffer等。它比 JSON 方案更可靠性能也更好。这是我目前最推荐的深拷贝方式。缺点是低版本浏览器不支持。如果项目跑在 Electron 或者较新的移动端 WebView 上可以放心用如果还需要兼容 iOS 15 以下的 Safari则需要考虑降级方案。4.4 手写深拷贝的思路和边界如果你既不想引第三方库又需要兼容老环境那就得手写一个深拷贝工具。核心思路是递归function deepClone(obj) { // 处理原始类型和 null if (obj null || typeof obj ! object) return obj; // 处理 Date if (obj instanceof Date) return new Date(obj.getTime()); // 处理 RegExp if (obj instanceof RegExp) return new RegExp(obj); // 处理数组 if (Array.isArray(obj)) { const arr []; for (let i 0; i obj.length; i) { arr[i] deepClone(obj[i]); } return arr; } // 处理普通对象 if (obj instanceof Object) { const newObj {}; for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] deepClone(obj[key]); } } return newObj; } throw new Error(无法深度拷贝该数据类型); }这个版本能覆盖大部分场景但还不能处理Map、Set、Symbol以及循环引用。实战中如果你要处理这些边缘数据建议直接引lodash的_.cloneDeep或者rfdc这类库别自己重复造轮子了。我在项目里落地的一个经验是深层数据拷贝优先交给第三方库浅层重复优先用structuredClone纯 JSON 数据才考虑JSON.parse(JSON.stringify())。优先级明确踩坑概率就大幅下降。5. 性能实测与大数据量优化数组重复在数据量小的时候各种方案之间的性能差异几乎可以忽略。但如果你在做一个数据看板或者处理的是后端一次性吐出的几千条甚至上万条记录方案之间的性能差距就会迅速拉大。5.1 实际上手压测一万条数据跑起来我在 Node.js 环境里用一个简单的console.time做过对比处理一个长度为10的数组重复1000次也就是最终数组长度10000左右结果如下方案耗时毫秒Array.from flatMap约1.2ms扩展运算符 flatMap约1.5msreduce concat约4.8ms纯 for 循环 push约0.8mspush(...baseList) 循环约1.0ms可以看出reduce concat确实是最慢的——它频繁创建新数组内存分配次数多。纯 for 循环 push 反而最快。而这边的数据量还不大如果把数据量提升到十万甚至百万差距会呈几何级放大。5.2 大数据量时的优化思路当最终数组长度超过十万时我再推荐几个优化点第一避免在循环里concat。每次concat都生成新数组属于 O(n^2) 的时间复杂度。正确做法是预先分配好结果数组的长度直接按下标写入function repeatFast(baseList, times) { const srcLen baseList.length; const result new Array(srcLen * times); for (let i 0; i times; i) { for (let j 0; j srcLen; j) { result[i * srcLen j] baseList[j]; } } return result; }这种写法在超大数据量的场景下性能可以比concat快一个数量级。第二如果数据是纯数值类型可以考虑使用TypedArray比如Float64Array它是真正的二进制缓冲区没有普通数组的封装开销。不过这会改变数据类型业务场景比较受限一般只有数值型图表数据才会用到。第三合理控制内存。重复一万份对象本身没问题但如果你每份对象里有嵌套三层的数据内存占用可能会爆炸。这时候应评估能不能用“按需计算”替代“提前复制”——比如渲染时动态生成而不是一次性构造出巨大的数组。5.3 性能与可维护性的平衡性能测试很重要但我必须说一句这些优化只有在真实业务出现卡顿时才需要。如果只是渲染几个卡片硬生生写上“预分配数组”这种代码反而降低可读性得不偿失。我的经验是**默认用Array.from flatMap满足可读性需求只有在数据量级上升到需要真正优化的时候才改成预分配数组的循环写法。**优化一定要以真实性能数据为依据而不是凭空猜测。6. 真实业务场景从“重复数组”到“能交付的代码”前面讲了这么多方案和理论最后落到实际业务里看看这玩意儿到底在哪用。我把自己写过的几种典型场景整理出来并给出完整的代码示例。6.1 场景一日历组件生成 35 格/42 格栅格日历组件是我遇到这个需求最多的场景。以 2024 年 2 月为例2月有29天第一天是周四。前端日历需要完整的 6 行 7 列也就是一个数组里要有 42 个日期格子。常见思路是先算出上个月补齐的空白格数量再生成当月的日期数组再补下个月的空白。这里就离不了数组拼接和重复const currentMonthDays Array.from({ length: 29 }, (_, i) i 1); // 头部补空白格周四之前3格 const headerPlaceholder Array(3).fill(null); // 尾部补空白格 const tailPlaceholder Array(10).fill(null); const allCells [...headerPlaceholder, ...currentMonthDays, ...tailPlaceholder]; // allCells 长度就是 3 29 10 42如果你要做一个垂直滚动的月历需要把每月的数据重复生成多份那Array.from({ length: months }, () monthArray)也是常规操作。6.2 场景二骨架屏批量占位卡片页面加载时的骨架屏通常需要展示一排灰色卡片。假设设计稿说“至少占满一屏”你要在前端根据屏幕宽度动态计算需要渲染多少张占位卡片const skeletonCount Math.ceil(window.innerWidth / 200); const skeletons Array.from({ length: skeletonCount }, (_, i) ({ id: skeleton-${i}, height: 180, width: 168 }));这里的核心还是“生成一个指定长度的数组”而且每个元素的字段还要带上下标。我倾向于用Array.from的映射参数。6.3 场景三批量复制表单行表单里有个“添加一行”按钮点一下就把上一行的数据复制一份然后清空部分字段。这种其实就是先取到上一行的数据然后做一次浅拷贝或者深拷贝Push 进去function addRow(rows, currentIndex) { const source rows[currentIndex]; const newRow Array.isArray(source) ? [...source] : { ...source }; rows.push(newRow); }这里判断一下Array.isArray是因为表单行可能是数组结构也可能是对象结构。如果你要同时复制多行那可以用前面的 repeat 方案const newRows Array.from({ length: 3 }, () ({ ...rows[rows.length - 1] }));注意这里的{ ...rows[rows.length - 1] }是浅拷贝只适用于表单行里没有嵌套对象的情况。6.4 场景四分页切片时的空状态占位有时候后端分页接口返回的数据不够一页前端要补齐剩余格子常见于宫格布局的Showcase页面const data [/* 已返回的6条数据 */]; const GRID_SIZE 12; const emptyCount GRID_SIZE - (data.length % GRID_SIZE || GRID_SIZE); const placeholders Array.from({ length: emptyCount }, () ({ isEmpty: true, text: 占位 })); const finalGrid [...data, ...placeholders];这种场景的关键点在于“补齐到特定数量”而不是简单的倍率重复。逻辑上比“重复N次”多一层计算但代码结构是一样的。7. 常见问题与排查技巧实录写到这里我把这些年做数组复制时踩过的坑、看同事踩过的坑以及社区里高频出现的问题统一整理成一个排查清单。下面每一条都是真实发生过的不是空谈。7.1 问题一fill 只执行一次不是每格都调用const result Array(3).fill(getNewObject());如果getNewObject()返回的是一个对象那么无论 fill 多少次数组里所有元素都是同一个引用。原因是 fill 会对传入的值直接赋值不会为每个槽位重新调用你的函数。解决方式用Array.from的 map 参数const result Array.from({ length: 3 }, () getNewObject());这是新手问得最多的问题。我见过一个同事在鉴权列表里用 fill 生成了多个令牌对象结果一个用户改了状态全线用户一起被踢下线。7.2 问题二map 和 filter 会跳过稀疏数组Array(3)创建的是稀疏数组通过[...Array(3)]可以转换成密集数组。但如果你直接调用.map()Array(3).map(() baseList) // 结果是 [空, 空, 空]不是你要的数组因为 map 遍历时会对稀疏数组的每个空槽做hasOwnProperty检查空槽直接跳过。这也是为什么Array.from比Array(n).map()更安全——它会把稀疏数组视为“每个槽位都是 undefined”然后正常遍历。7.3 问题三flatMap 只能展开一层flatMap是map后跟flat(1)的简写。如果你的数组元素本身还是个数组对象展开一层刚好。但如果你嵌套很深比如要把三维数组变成一维那就要用flat(Infinity)const nested [[[1, 2]], [[3, 4]]]; nested.flat(Infinity); // [1, 2, 3, 4]在数组重复的场景里一般最多只需要展开一层所以flatMap是够用的。如果用了flat(Infinity)反而会有性能隐患。7.4 问题四对象数组去重和重复其实是邻居需求热搜词里有个“对象数组去重”和“重复多次”看起来相反但实际经常一起出现。比如你先复制了一堆数据又发现里面有重复项得去重function uniqueByKey(arr, key) { const seen new Set(); return arr.filter(item { const value item[key]; if (seen.has(value)) return false; seen.add(value); return true; }); }如果你在写完 repeat 逻辑之后发现结果里有重复项消化掉这个函数就够了。7.5 问题五记不住方法干脆封装一个 repeat 工具函数最后这个建议没什么高深的纯粹是从工程效率出发的。如果你在一个项目里反复复制数组写五六个方案不如统一封装一个函数。const util { repeatArray(arr, times, deep false) { if (!Array.isArray(arr)) throw new Error(repeatArray requires an array); if (times 0) return []; const source deep ? structuredClone(arr) : arr; return Array.from({ length: times }, () source).flat(); }, repeatGroups(arr, times) { return Array.from({ length: times }, () [...arr]); } };这样团队里所有人用同一套实现不会出现一个人用 fill、另一个人用 reduce 的混乱局面。8. 接口数据场景和后端 Array 对象互相配合我们项目做前端隔离后端接口经常返回“数组对象”而且有时候返回的数组里套着数组对象。比如一个订单列表接口const apiResponse { code: 0, data: { list: [ { orderId: 1, items: [{ name: 可乐, price: 3 }] }, { orderId: 2, items: [{ name: 薯片, price: 5 }] } ] } };在这种数据结构下做重复复制浅拷贝的坑就更隐蔽了。你复制的是list数组但每个order里的items还是同一个引用。我在移动端 H5 页面就遇到过复制一份订单展示到两个区块用户在其中一个区块取消了某个商品另一个区块的商品也没了。最后排查到是复制时只做了[...orderList]的浅拷贝内层的items完全共享。遇到这种套娃结构我的建议是别用 JSON 序列化走极端也别天真地只用一层展开直接上structuredClone或者lodash.cloneDeep从根上规避问题。9. 和其他端侧逻辑的交互提醒热搜词里有个“oc和javascript互相调用”这让我想起在 iOS WebView 的 JavaScript Bridge 环境里踩过的一个雷。在移动端 WebView 里Javascript 数组对象传给原生 OC 代码时往往需要先把数组转换成 JSON 字符串再通过 bridge 传递。如果你对这个数组做了重复复制且没有做深拷贝传给原生时先天就有引用共享的隐患——原生修改了某个元素值网页里被“复制”出来的另一份数据也会变。我当时用了一个笨办法把重复后的数组做一次JSON.stringify再JSON.parse也就是所谓“双重序列化”。在纯数据对象场景下这个笨办法反而是最稳妥的。如果你在 Chrome DevTools 的 Elements 面板或者其他监控环境里看到类似“重负载 JavaScript”的警告第一个要检查的就是是不是你的重复复制逻辑里做了过大的深拷贝导致页面长时间卡顿。这类性能问题和业务 bug 一样值得重视。10. 快进到结论能落地的代码模板写了这么多行代码和踩坑记录最后送各位一份可以直接带走的模板。这个模板是我在多个前端项目里实际用的最终版兼顾可读性、兼容性和灵活性/** * 让一个数组对象重复多次 * param {Array} arr 原始数组 * param {number} times 重复次数 * param {Object} [options] 配置项 * param {boolean} [options.deepfalse] 是否深拷贝数组内部对象 * param {boolean} [options.groupedfalse] 是否返回嵌套数组 * param {Function} [options.mappernull] 对每个重复单元执行的自定义映射 * returns {Array} */ function repeatArray(arr, times, options {}) { const { deep false, grouped false, mapper null } options; if (!Array.isArray(arr)) { throw new TypeError(Expected an array as first argument); } if (times 0) return []; // 深拷贝整个数组确保内部对象独立 const base deep ? structuredClone(arr) : arr; // 生成重复后的二维数组 const groups Array.from({ length: times }, () { const clone grouped ? base.slice() : base; return mapper ? mapper(clone) : clone; }); return grouped ? groups : groups.flat(); } // 用法示例 const list [{ id: 1 }, { id: 2 }]; // 拼接式重复浅拷贝 repeatArray(list, 3); // [{ id: 1 }, { id: 2 }, { id: 1 }, { id: 2 }, { id: 1 }, { id: 2 }] // 拼接式重复深拷贝 repeatArray(list, 3, { deep: true }); // 嵌套式分组 repeatArray(list, 3, { grouped: true }); // [[{ id: 1 }, { id: 2 }], [{ id: 1 }, { id: 2 }], [{ id: 1 }, { id: 2 }]] // 每次复制时给数据加批号 repeatArray(list, 3, { mapper: (group, index) group.map(item ({ ...item, batch: index })) });这个函数把前面五大方案的优点都吸收进来了Array.from负责生成固定长度的骨架flat负责摊平structuredClone负责深拷贝mapper负责扩展开销。从我个人的工程经验来看真正靠谱的代码不是最骚气的而是“别人接手能看懂、线上环境不爆雷、将来好扩展”的版本。上面这个模板就是朝着这个方向做的。如果你现在正在写一个日历组件、一个骨架屏、一个分页器或者任何需要“把一段数据重复展示”的功能直接把这段代码复制到你的工具类里替换掉以前的for循环和concat至少在数据独立性和代码可读性这两件事上你会比之前的自己稳很多。
返回列表