在Javascript函数中的reduce之后,for循环的行为令人费解



问题:

  • For函数内部的循环返回一个无法解释的结果(第一张图片,在第150行用黄色突出显示(
  • 与粘贴在函数外部的循环副本相同,返回预期结果(第二张图片,第164行用紫色突出显示(
  • 我不是在寻找关于算法问题方法的反馈,只是试图理解以上内容

上下文:

  • 岩石剪刀算法问题,根据作为参数传入的回合数,返回一个嵌套数组数组,表示可能的手的排列
  • 函数是递归的,以"n"个循环作为参数,[[]]作为默认参数
  • 第136-142行:因为可能的总排列数是3^n,所以它采用默认参数并将每个元素复制3次(newOutput(
  • 第146-149行:对于循环通过newOutput,在第一个数组中添加"rock",在第二个数组中增加"paper",在在第三个嵌套数组中加"scapper",依此类推

问题

行136中元素的重复影响了146处for循环的结果,但为什么呢?这是无法解释的,因为执行线程已经移到了第146行。


第一张图片

第二张图片

以下代码:

function rockPaperScissors(num, output = [[]]) {
// when num is 0, return output
if (num === 0) return output;
const moves = ['rock', 'paper', 'scissors']
// take output parameter, and duplicate each of the existing nested array 3 times 
const newOutput = output.reduce((acc, curr) => {
let i = 1;
while (i <= 3) {
acc.push(curr);
i++
}
return acc;
}, [])
// iterate through newOutput, push to each nested array rock first, then paper, then scissors, so on 
for (let i = 0; i < newOutput.length; i++) {
newOutput[i].push(moves[i%3]);
}
return rockPaperScissors(num - 1, newOutput);
}
rockPaperScissors(1)
// should return [[rock], [paper], [scissors]]
// instead, returns [[rock, paper, scissors], [rock, paper, scissors], [rock, paper, scissors]]

之所以会发生这种情况,是因为您将SAME元素(Object(输出[0]n次推入累加器,创建它的n克隆,该克隆后来成为newOutput。由于这些表示相同的实例,在其中一个实例上执行的每个操作都会对其他实例产生副作用。


如何解决

您可以通过使用排列运算符来解决此问题,该运算符将数组的值复制到新的值中。

...
acc.push([...curr]);
...

一些不错的阅读:https://javascript.info/object-copy

这是因为:

while (i <= 3) {
acc.push(curr);   // these are not copies, it's all the same array
i++
}

这不是创建curr数组的副本,它只是推送相同的引用3次。所有这3个元素都引用(或指向(相同的底层数组。你有这样的东西:

[  ref_0,   ref_1,   ref_3  ]
|         |        |
|         |        |
`---------`--------`--------------> []

当您试图通过其中一个引用修改指向数组的内容时;参见";改变,因为它都是相同的阵列:

[  ref_0,   ref_1,   ref_3  ]
|         |        |
|         |        |
`---------`--------`--------------> ['rock', 'paper', 'scissors']

下面的可运行片段演示了这一点。

请注意,您可以将每个引用替换为其他内容;只有当你修改它们指向的数组的内容时,问题才会变得明显。换句话说,你可以这样做:

newOutput[0] = "something else"
[  ref_0,   ref_1,   ref_3  ]
|         |        |
|         |        |
|          `--------`--------------> ['rock', 'paper', 'scissors']
| 
`---------> "something else"

如果需要副本,则必须使用排列运算符显式生成副本。假设output中的内部对象始终是一个数组,您可以这样复制它:

acc.push([...curr])

如果你没有令人信服的理由使用reduce,你可以这样做:

// [...Array(3).keys()]  produces the range [0, 1, 2]
// Use that with .map to create your copies.
const newOutput = [...Array(3).keys()].map(i => [...output[0]]);

const output = [['Array Content']];
const newOutput = output.reduce((acc, curr) => {
let i = 1;
while (i <= 3) {
acc.push(curr);   // these are not copies, it's all the same array
i++
}
return acc;
}, []);

displayInDiv('output-div-1', newOutput);
// shows: [["Array Content"],["Array Content"],["Array Content"]]

// Since all elements refer to the same underlying array, 
// modifying its contents through one of them affects all of them
output[0][0] = 'Modified Content';
displayInDiv('output-div-2', newOutput);
// shows: [["Modified Content"],["Modified Content"],["Modified Content"]]
displayInDiv('output-div-3', newOutput[0] === newOutput[1]);
displayInDiv('output-div-4', newOutput[1] === newOutput[2]);
// shows true in both cases
// it means that it's all the same object

function displayInDiv(divId, objectToShow) {
const div = document.getElementById(divId);
div.innerText = JSON.stringify(objectToShow);
}
div {
margin-top: 4px;
}
.description {
margin-top: 16px;
}
.output {
color: gray;
font-family: 'Courier New', monospace;
}
<div class="description">Result of reduce: </div>
<div id="output-div-1" class="output"></div>
<div class="description">After modification of array content: </div>
<div id="output-div-2" class="output"></div>
<div class="description">newOutput[0] === newOutput[1]: </div>
<div id="output-div-3" class="output"></div>
<div class="description">newOutput[1] === newOutput[2]: </div>
<div id="output-div-4" class="output"></div>

最新更新