嵌套的 forEach 循环意外行为

Nested forEach loops unexpected behavior

在我的扑克应用程序中,我有一组手牌,每手牌都是随机选择的具有价值和花色的纸牌对象的数组:

[ [ { value: 5, suit: 's' },
    { value: 4, suit: 's' },
    { value: 6, suit: 'c' },
    { value: 11, suit: 'd' },
    { value: 12, suit: 'c' } ],
  [ { value: 9, suit: 'd' },
    { value: 12, suit: 'h' },
    { value: 8, suit: 'c' },
    { value: 12, suit: 's' },
    { value: 2, suit: 's' } ],
  [ { value: 4, suit: 'h' },
    { value: 6, suit: 's' },
    { value: 10, suit: 'c' },
    { value: 3, suit: 'd' },
    { value: 7, suit: 'd' } ] ]

为了准备评估手,我想 return 一个手对象数组,每个对象都有一个 valuessuits 数组。所以输出将是:

[ 
  { 
    values: [5, 4, 6, 11, 12],
    suits: ['s', 's', 'c', 'd', 'c'] 
  },      
  { 
    values: [9, 12, 8, 12, 2], 
    suits: ['d', 'h', 'c', 's', 's'] 
  },
  { 
    values: [4, 6, 10, 3, 7],
    suits: ['h', 's', 'c', 'd', 'd'] 
  } 
]

我正在尝试使用嵌套的 forEach 循环来实现这一点,如下所示:

let temp = []
hands.forEach((el) => {
  temp = el
  el = {}
  el.values = []
  el.suits = []
  temp.forEach((obj) => {
    el.values.push(obj.value)
    el.suits.push(obj.suit)
    console.log(el) //expected output
  })
  console.log(el) //expected output
})
console.log(hands) //same as original

然而,正如评论所述,它的行为符合预期,直到循环结束,其中 hands 根本没有改变。我在这里错过了什么?

hands.forEach((el) => {

您将数组中的每个对象依次分配给变量 el

el = {}

您用对新对象的引用覆盖了变量el

el不再连接到它的原始值。

只是替换了el的值。数组中的属性仍然指向原始对象。


要更改原始数组中的对象,您需要为它们显式分配一个新对象。

hands.forEach((originalValue, index, array) => {
  // originalValue is what you used to call `temp`
  // Just make `el` a new object
  var el = {};
  // Put it in the array
  array[index] = el;
  // Then continue as before

forEach 不会更改调用它的数组,它只是迭代数组,为每个元素调用一个函数。如果你想在该函数中构建一个新数组,就这样做

var newArray = [];
hands.forEach((el) => {
   var newValue = // do something here
   newArray.push(newValue);
});

我想你想做的是:

var hands = [ [ { value: 5, suit: 's' },
    { value: 4, suit: 's' },
    { value: 6, suit: 'c' },
    { value: 11, suit: 'd' },
    { value: 12, suit: 'c' } ],
  [ { value: 9, suit: 'd' },
    { value: 12, suit: 'h' },
    { value: 8, suit: 'c' },
    { value: 12, suit: 's' },
    { value: 2, suit: 's' } ],
  [ { value: 4, suit: 'h' },
    { value: 6, suit: 's' },
    { value: 10, suit: 'c' },
    { value: 3, suit: 'd' },
    { value: 7, suit: 'd' } ] ];

let newValues = []
hands.forEach((el) => {
  var temp = {values:[], suits:[]};
  el.forEach((obj) => {
    temp.values.push(obj.value)
    temp.suits.push(obj.suit)
    
  })
  newValues.push(temp);
})
console.log(newValues) // NOT same as original

有很多方法可以实现同样的事情,我能想到的最好的方法如下 - 并且避免了其他答案中出现的双重 hand.map(效率很低)。

var hands = [ [ { value: 5, suit: 's' },
    { value: 4, suit: 's' },
    { value: 6, suit: 'c' },
    { value: 11, suit: 'd' },
    { value: 12, suit: 'c' } ],
  [ { value: 9, suit: 'd' },
    { value: 12, suit: 'h' },
    { value: 8, suit: 'c' },
    { value: 12, suit: 's' },
    { value: 2, suit: 's' } ],
  [ { value: 4, suit: 'h' },
    { value: 6, suit: 's' },
    { value: 10, suit: 'c' },
    { value: 3, suit: 'd' },
    { value: 7, suit: 'd' } ] ];

let newValues = hands.map(h => {
      return h.reduce( (p,c) => {
            p.values.push(c.value);
            p.suits.push(c.suit);
            return p;
        },{values:[], suits:[]});
  });

console.log(newValues) // NOT same as original

利用数组支持的 map 函数,让 JavaScript 为您代劳,而不是编写大量代码:

let rewritten = data.map(hand => {
  return {
    values: hand.map(card => card.value),
    suits: hand.map(card => card.suit),
  } 
});

(如果将箭头函数中的外部 { return} 全部放在同一行,则不需要,但出于答案的目的,它的可读性大大降低当然)

这会获取您的原始数据,这些数据已经是手上的数组大小(在本例中为三个),然后对每只手应用转换。具体来说,它创建了一个新对象,其中 "values and suits" 的数组已变成 "just values" 的数组,使用 map,"just suits" 的数组,也使用 map.

JavaScript 中的数组附带了一些 packing/unpacking/iterating/transforming 的实用函数,值得一读。它们允许您用很少的代码做大事。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array 值得一读,如果您有一段时间或从未阅读过关于数组的 API。它还解释了如何使用 recude,例如,它可用于提高此代码的效率(请参阅上面 Jamiec 的回答)。

您可以将值直接映射到属性的对象中。

var data = [[{ value: 5, suit: 's' }, { value: 4, suit: 's' }, { value: 6, suit: 'c' }, { value: 11, suit: 'd' }, { value: 12, suit: 'c' }], [{ value: 9, suit: 'd' }, { value: 12, suit: 'h' }, { value: 8, suit: 'c' }, { value: 12, suit: 's' }, { value: 2, suit: 's' }], [{ value: 4, suit: 'h' }, { value: 6, suit: 's' }, { value: 10, suit: 'c' }, { value: 3, suit: 'd' }, { value: 7, suit: 'd' }]],

result = data.map(a => (
  { 
    values: a.map(b => b.value), 
    suits: a.map(b => b.suit) 
  })
);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

您可以使用较少的迭代样式。

var data = [[{ value: 5, suit: 's' }, { value: 4, suit: 's' }, { value: 6, suit: 'c' }, { value: 11, suit: 'd' }, { value: 12, suit: 'c' }], [{ value: 9, suit: 'd' }, { value: 12, suit: 'h' }, { value: 8, suit: 'c' }, { value: 12, suit: 's' }, { value: 2, suit: 's' }], [{ value: 4, suit: 'h' }, { value: 6, suit: 's' }, { value: 10, suit: 'c' }, { value: 3, suit: 'd' }, { value: 7, suit: 'd' }]],
    keys = ['value', 'suit'],
    result = data.map(
        a => a.reduce(
            (r, b) => (keys.forEach(k => r[k + 's'].push(b[k])), r), { values: [], suits: [] }
        )
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

您只需要在第二个循环之前创建一个具有两个属性(值和花色)的对象,然后将他注入到第二个循环之后的结果中,如下所示:

var hands = [[{ value: 5, suit: 's' }, { value: 4, suit: 's' }, { value: 6, suit: 'c' }, { value: 11, suit: 'd' }, { value: 12, suit: 'c' }], [{ value: 9, suit: 'd' }, { value: 12, suit: 'h' }, { value: 8, suit: 'c' }, { value: 12, suit: 's' }, { value: 2, suit: 's' }], [{ value: 4, suit: 'h' }, { value: 6, suit: 's' }, { value: 10, suit: 'c' }, { value: 3, suit: 'd' }, { value: 7, suit: 'd' }]];
var output = [];
hands.forEach(x => {
  var temp = {values: [], suits: []};
  x.forEach(y => {
    temp.values.push(y.value);
    temp.suits.push(y.suit);
  })
  output.push(temp);
});
console.log(output);