Array.prototype.fill()填充的数组表现异常

12

我遇到了一个关于数组的问题,我创建了一个数组并填充了空的子数组,以获取一个二维矩阵。但是当我操作这个数组时,它的行为不符合我的预期。

var arr = new Array(5);
arr.fill([]);
arr[2].push("third rank item");
console.log(arr);

//[ [ 'third rank item' ],
//  [ 'third rank item' ],
//  [ 'third rank item' ],
//  [ 'third rank item' ],
//  [ 'third rank item' ] ]

对于这个问题,任何意见都将受到欢迎。


你希望它如何表现? - Feathercrown
2
你正在使用相同的空数组进行填充。你可以尝试使用Array.from(new Array(5), () => []) - user663031
@torazaburo,你能简要解释一下吗?OP针对外部数组中的第三个项目,并推入一个字符串,导致在所有内部数组中都推入了该字符串。有点令人困惑。 - Rajaprabhu Aravindasamy
所有内部数组都是同一个数组。 - user663031
所有内部数组都是同一个数组。 - user663031
9个回答

20

这是关于数组问题(和对象问题)的老生常谈,即它们是引用而不是值。

具体而言,当你执行 arr.fill([]) 时,你是拿一个空数组来填充父数组。

这就像在说:

var arr = new Array(5);
arr[0] = arr[1] = arr[2] = arr[3] = arr[4] = [];

它们都指向同一个数组!因此,当你修改其中一个时,它们看起来都被修改了(但实际上仍然是同一个)

不幸的是,没有简单的方法将空数组分配给每个变量。你可以尝试做类似这样的事情:

Array.apply(null, Array(5)).map(function() {return [];});

基本上,创建一个长度为5的(已初始化的)空数组,并将每个(空)值映射到一个新的[]

编辑:根据@torazaburo的评论,您可以使用Array.from代替Array.apply(null, Array(5)).map,像这样:

Array.from( new Array(5), function() { return []; } );

1
让 arr = Array.from(Array(n), () => []); 在这个例子中,n 的值为 5。 - Edward Moffett

2

使用array.fill方法填充数组时,你会注意到数组中每个元素都指向同一个数组的引用。

如果你想要将每个数组索引实例化为一个空数组,则可以使用普通的while循环:

var arr = [];
var n = 5
while(n--)
  arr[n] = []

arr[2].push("third rank item");
console.log(arr);

选项2:
如果您有可用的lodash包,您也可以使用_.map,因为它是专门设计用于循环遍历稀疏数组的(原生map将跳过未初始化的值)

var arr =_.map(new Array(5), (x => []))

arr[2].push("third rank item");
console.log(arr)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>


2
ECMA文档中,Array.prototype.fill的第11行清楚地给出了这个谜团的原因。

重复以下步骤,直到 k < final:

令 Pk 为 ToString(k)。

令 setStatus 为 Set(O, Pk, value, true)。

如果出现异常,就立即返回结果。

将 k 的值加 1。

这里的"value"只是一个引用接收到的参数。他们将其作为一个属性直接设置到数组中。这意味着所有填充好的数组只是对一个单独的数组对象的引用。

2

这是由于引用的原因。数组是对象的一种类型,对象在处理其引用时工作,当您使用[]或new Array()填充数组时,只运行一次并将相同的数组放入所有索引中,因此当您更新子数组时,所有子数组都会被更新。

解决方法:

let arr = new Array(5).fill(0).map(ele => ele = []); arr[2].push("something");

或者

let arr = Array.of([], [], [], []); arr[2].push("something");

结果: 只有第2个索引的arr被更新,与预期一致。


1

使用ES6,我建议使用以下方法创建2维或多维数组:

// create an M x N dimension grid and fill it with 0's
const myArray = [...Array(M)].map(r => [...Array(N)].map(r => 0));

0
你可以尝试这个,

var arr = new Array(5);
var i = 0;
while (i < arr.length)
  arr.fill([], i++);
arr[2].push("third rank item");
console.log(arr);


0

试试这个,这是一个一行代码的快速解决方案。

var arr = new Array(5);
arr = Array.from(arr, x => []);
arr[2].push("third rank item");
console.log(arr);


0
有点不同:
let array= JSON.parse(JSON.stringify(new Array(N).fill([])));

或者

let array=new Array(N).fill(null).map(()=>[]);

-1
所有的答案都是正确和合理的,所以我决定在我的Chrome控制台上尝试一下。因此,对于OP的答案是,这是因为您无法直接填充。
如果我理解问题的一部分是为什么不能只做:
const grid = Array(5).fill([])

这段话的意思是:好的,我们有一个像这样的结构:

//[ [ ],
//  [ ],
//  [ ],
//  [ ],
//  [ ] ]

然后你说去这样填写第三个:

grid[2].push("third rank item");

但是,你没有看到第三个填满该字符串,而是所有的都被填充了。原因很简单,你创建了一个单一的数组并将其扔到了网格中的每个位置。在内存中只有一个内部数组,所以当你把这个字符串扔到那个数组里时,它会影响每个索引。

但我发现有些行为与我预期的略有不同。

如果您编写以下内容:

const grid = Array(3).fill([false, false, false]);

你将会得到以下内容:

(3) [Array(3), Array(3), Array(3)]
(3) [false, false, false]
(3) [false, false, false] 
(3) [false, false, false]

然后如果你运行:grid[0].push(true),你会得到以下结果:
(3) [Array(4), Array(4), Array(4)]
(4) [false, false, false, true]
(4) [false, false, false, true]
(4) [false, false, false, true]

因此,修改一个会修改所有内容,这就是为什么你不能像之前那样直接填充然后推送,而是必须使用map()语句,如下所示:

const grid = Array(3)
  .fill(null)
  .map(() => Array(3).fill(false));

这将运行内部函数三次,每次我们都会生成一个全新且不同的数组。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接