'for in' 在 JS 中遍历数组的问题

Problem with 'for in' loop over arrays in JS

今天我遇到了一个问题,我解决了它,但我不知道为什么会这样!所以我决定在这里问一下。

让我快速而简短地展示我的问题:

function newProp(array) {
  let returnArray = [];
  for (i in array) {
    returnArray.push(i);
  }
  return returnArray;
}

let underTest = [{
  username: 'Whosebug',
  email: 'Whosebug@noStack.com',
  total: 2
}]
let input = [1, 2, 3, 4];

for (i in underTest) {
  console.log('before call:', underTest[i]);
  underTest[i].permutation = newProp(input);
  console.log('after call the underTest[i] does not exist:', underTest[i]);
  console.log('But the whole array still exists', underTest);
}

代码解释: 我正在尝试向 underTest 数组的每个对象添加一个名为 permutation 的新 属性。

问题: 我不希望 underTest[i] 在调用 newProp() 函数后变成 undefined

我的问题:为什么调用newProp()underTest[i]undefined?我认为这是因为在我的代码中使用 for in over array 但在 MDN web doc 中没有关于我的问题的提示,它只是说:

Note: for...in should not be used to iterate over an Array where the index order is important.

Array indexes are just enumerable properties with integer names and are otherwise identical to general object properties. There is no guarantee that for...in will return the indexes in any particular order. The for...in loop statement will return all enumerable properties, including those with non–integer names and those that are inherited.

Because the order of iteration is implementation-dependent, iterating over an array may not visit elements in a consistent order. Therefore, it is better to use a for loop with a numeric index (or Array.prototype.forEach() or the for...of loop) when iterating over arrays where the order of access is important.

并且由于上面的段落,虽然我不介意元素的顺序,但我应该能够使用 for in 结构(我也不介意继承的属性)。

解决方案: 通过反复试验,我找到了很多解决问题的方法,我认为提及其中一些是有用的:

  1. for in替换为传统的for循环即可解决问题:

function newProp(array) {
  let returnArray = [];
  for (i in array) {
    returnArray.push(i);
  }
  return returnArray;
}

let underTest = [{
  username: 'Whosebug',
  email: 'Whosebug@noStack.com',
  total: 2
}]
let input = [1, 2, 3, 4];

for (let i = 0; i < underTest.length; i++) {
  console.log('before call:', underTest[i]);
  underTest[i].permutation = newProp(input);
  console.log('after call the underTest[i] exists', underTest[i]);
}

  1. newProp 函数中,如果不是动态创建数组,而是 return 静态数组,那么问题就消失了(我还不知道为什么!):

function newProp(array) {
  return [1, 2, 3, 4];
}

let underTest = [{
  username: 'Whosebug',
  email: 'Whosebug@noStack.com',
  total: 2
}]
let input = [1, 2, 3, 4];

for (i in underTest) {
  console.log('before call:', underTest[i]);
  underTest[i].permutation = newProp(input);
  console.log('after call the underTest[i] exists', underTest[i]);
}

  1. 你可以使用 underTest 数组的内置 forEach 方法,它也能解决问题,但我不喜欢它,因为它会导致 回调地狱 问题(尤其是当您使用异步作业时)。所以我没有提供它的片段。

注意:如果可以,请说明为什么第二种方案也能解决问题?

正如@HaoWu 和@VLAZ 评论的那样,我的问题与 for in 循环无关。这是因为将 i 变量声明为全局变量。

如果我将 i 变量的声明更改为局部变量,问题就会消失:

function newProp(array) {
  let returnArray = [];
  for (let i in array) {
    returnArray.push(i);
  }
  return returnArray;
}

let underTest = [{
  username: 'Whosebug',
  email: 'Whosebug@noStack.com',
  total: 2
}]
let input = [1, 2, 3, 4];

for (let i in underTest) {
  console.log('before call:', underTest[i]);
  underTest[i].permutation = newProp(input);
  console.log('after call the underTest[i] exists:', underTest[i]);
}

更新:

正如@3limin4t0r 在这个答案下面提到的:如果你不需要元素的索引,最好使用 for of 结构而不是 for in:

function newProp(array) {
  let returnArray = [];
  for (let i in array) {
    returnArray.push(i);
  }
  return returnArray;
}

let underTest = [{
  username: 'Whosebug',
  email: 'Whosebug@noStack.com',
  total: 2
}]
let input = [1, 2, 3, 4];

for (let element of underTest) {
  console.log('before call:', element);
  element.permutation = newProp(input);
  console.log('after call the underTest[i] exists:', element);
}