侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 123 篇文章
  • 累计收到 7 条评论

fill({}) 造出来的三行,改一行为什么会三行一起变

2026-9-25 / 0 评论 / 5 阅读

fill({}) 造出来的三行,改一行为什么会三行一起变

一张三行的草稿表格,每行先建成空对象,等着按顺序把数据填进去。最省事的写法是 new Array(3).fill({}),一行写完。填完第一行回头核对,三行内容一模一样,连还没动过的后两行也一样。

① new Array(3).fill({}) 改第一项
   rows = [{"id":1},{"id":1},{"id":1}]
   rows[0] === rows[1] : true
   rows[1] === rows[2] : true

两条 true 把问题定位到「三个格子里装的是同一个对象」这件事上,和循环没关系。

是循环写错了吗

fill 的语义是把同一个值放进每一格,它不做复制。对象在 JS 里按引用传递,同一个值就是同一个引用,改某一格的属性等于改这一个对象,其余格子读到的自然是改过的内容。

把 fill 换成 Array.from 再看一遍就能确认。第二个参数是回调,每一格调一次,回调里新建的对象彼此独立。

② Array.from({length:3}, () => ({})) 改第一项
   rows2 = [{"id":1},{},{}]
   rows2[0] === rows2[1] : false

值类型为什么不一起变

同样的写法换成数字就没这个问题。数字和字符串在数组里存的是值本身,改一格等于替换这一格的内容,其他格子没有理由跟着动。字符串本身是引用类型,但它不可变,改一格只能整体替换,共用同一个字符串不会带来副作用,所以真正要小心的是那些可以被就地修改的对象。判断依据是填进去的东西能不能被改,而不是 fill 的用法对不对。

③ new Array(3).fill(new Array(2).fill(0)) 再改 [0][0]
   grid = [[9,0],[9,0],[9,0]]
④ 值类型填充后改第一项

③ 那一段是二维表格:内层数组同样只建了一个,改 grid[0][0] 的时候三行一起变成 [9,0]。

这种初始化在草稿表格里很常见:接口字段还没到齐,先把 n 行占位建出来,等数据回来按 id 回填。占位三行如果是同一个对象,回填就等于只填了一行,剩下两行跟着显示同样的内容,看上去像接口把同一份数据回了三遍,排查方向很容易被带偏到接口那一侧。

空槽数组的 map 一次都不回调

new Array(3) 造出来的数组有三个空槽,空槽和 undefined 不是一回事。map 和 forEach 只处理真实存在的下标,空槽会被跳过。

⑤ new Array(3).map 的回调次数
   回调次数 = 0 | mapped = [null,null,null]
   下标 0 在数组里吗 : false
⑥ new Array(3).fill(0).map 的回调次数
   回调次数 = 3 | mapped2 三项互不相同 : true

回调次数是 0,下标 0 在数组里吗 是 false。打印出来的 [null,null,null] 是 JSON.stringify 把空槽写成 null 的结果,数组本身还是三个空槽,这也是 new Array(3).map(fn) 什么都没做却看不出异常的原因。

空槽还会骗过长度检查:new Array(3).map(fn) 的结果长度是 3,遍历长度挑不出问题,而里面一格都没算过。空槽数组交给 for...of 或展开语法时按 undefined 处理,交给 map 时直接跳过,同一份数组在两种写法下表现不同,这一点比空值本身更容易踩。

new Array(3).fill(0).map(fn) 的回调次数是 3:先把每一格填成真实存在的 0,后面才轮得到映射。

⑦ 攒完三行之后
   table = [{"name":"丙","qty":3},{"name":"丙","qty":3},{"name":"丙","qty":3}]

最后一段是用循环往同一个对象里填三行之后的样子,三行内容一致,字段被后一次赋值覆盖。这种写法不抛异常,也不报错,表现就是数据互相顶掉。

按行的独立与否选写法

需要每行互相独立的对象,用 Array.from({length:n}, () => ({}));二维表格要记得内层也建 n 次。只需要一个填满数字的数组用来累加,new Array(n).fill(0) 更直接,也省掉一次回调。Array.from 的回调第二个参数是下标,按行号生成字段时可以少写一次遍历,fill 没有这个位置。

要提前发现也简单,初始化完立刻比一次引用:rows[0] === rows[1] 是 true 就说明只建了一个对象。或者先给第一行填一个临时字段再看其余行,两边一起变就等于确认了。这两种都是一行代码的检查,比在页面上翻半天快。

我现在的取舍是:要放对象的表格不用 fill,值类型的默认数组才用它。

这类写法带来的问题通常在下游才暴露,等到读到一个不该出现的字段,才回头怀疑这行看起来很短的初始化代码。回到开头那份草稿表格,把 fill({}) 改成 Array.from,三行才是真的三行。

    🤞 分享