JS/NodeJS 中 for 循环的奇怪行为

Strange behavior in for-loop in JS/NodeJS

我正在开发一个 NodeJS/VueJS 应用程序,我遇到了一些奇怪的行为,我不确定这是我做错了什么还是其他原因。

我有 2 个数组,一个包含活动的对象数组和一个包含与所有活动关联的所有促销的对象数组。

此时,每个 Campaign 对象都包含一个名为 promos 的空数组,我想在其中推送每个活动的所有促销活动。

for (var i = 0; i < campaigns.length; i++) {
    for (var j = 0; j < campaignsPromo.length; j++) {
        if (campaignsPromo.length > 0) {
            if (campaigns[i].IDCampaign == campaignsPromo[j].IDCampaign) {
                if ((campaignsPromo[j].associated == 1) && (campaignsPromo[j].validated == 1)) {
                    campaigns[i].promos.push(campaignsPromo[j]);

                    console.log("Validated Campaign ID " + campaigns[i].IDCampaign);
                    console.log("Promo ID " + campaignsPromo[j].IDPromo);
                } else if ((campaignsPromo[j].associated == 1) && (campaignsPromo[j].validated == 0)) {
                    campaigns[i].unvalidatedPromos++;

                    console.log("Unvalidated Campaign ID " + campaigns[i].IDCampaign);
                    console.log("Promo ID " + campaignsPromo[j].IDPromo);
                }
            }
        } else {
            console.log("No promos!");
        }
    }
}

起初,代码似乎在做应该做的事情,它检查了我的测试数据集。 然而,最后,所有的活动最终都有相同的促销活动。

Campaigns With No Promos: [{"IDCampaign":7,"campaignName":"dadsadafds","startDate":"2022-02-03","endDate":"2022-02-28","unvalidatedPromos":0,"promos":[]},{"IDCampaign":3,"campaignName":"Tarzan 3","startDate":"2022-02-02","endDate":"2022-02-06","unvalidatedPromos":0,"promos":[]},{"IDCampaign":1,"campaignName":"Tarzan","startDate":"2022-02-01","endDate":"2022-03-01","unvalidatedPromos":0,"promos":[]},{"IDCampaign":2,"campaignName":"Tarzan 2","startDate":"2022-02-01","endDate":"2022-02-08","unvalidatedPromos":0,"promos":[]},{"IDCampaign":4,"campaignName":"Tarzan 4","startDate":"2022-02-01","endDate":"2022-02-05","unvalidatedPromos":0,"promos":[]},{"IDCampaign":5,"campaignName":"Jabe 1","startDate":"2022-02-01","endDate":"2022-02-05","unvalidatedPromos":0,"promos":[]},{"IDCampaign":6,"campaignName":"dadsada","startDate":"2022-02-01","endDate":"2022-02-08","unvalidatedPromos":0,"promos":[]},{"IDCampaign":8,"campaignName":"Black Friday","startDate":"2022-02-01","endDate":"2022-02-28","unvalidatedPromos":0,"promos":[]}]

Validated Campaign ID 1
Promo ID 1119
Unvalidated Campaign ID 1
Promo ID 107

Campaigns With Promos: [{"IDCampaign":7,"campaignName":"dadsadafds","startDate":"2022-02-03","endDate":"2022-02-28","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":3,"campaignName":"Tarzan 3","startDate":"2022-02-02","endDate":"2022-02-06","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":1,"campaignName":"Tarzan","startDate":"2022-02-01","endDate":"2022-03-01","unvalidatedPromos":1,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":2,"campaignName":"Tarzan 2","startDate":"2022-02-01","endDate":"2022-02-08","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":4,"campaignName":"Tarzan 4","startDate":"2022-02-01","endDate":"2022-02-05","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":5,"campaignName":"Jabe 1","startDate":"2022-02-01","endDate":"2022-02-05","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":6,"campaignName":"dadsada","startDate":"2022-02-01","endDate":"2022-02-08","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]},{"IDCampaign":8,"campaignName":"Black Friday","startDate":"2022-02-01","endDate":"2022-02-28","unvalidatedPromos":0,"promos":[{"IDCampaign":1,"IDPromo":1119,"promoType":"CHRISTMASS","associated":1,"validated":1,"promoName":"Test Promo 1","beginDate":"2020-11-26","endingDate":"2020-11-29"}]}]

这是campaignsPromo的内容:

[ { IDCampaign: 1,
    IDPromo: 1119,
    promoType: 'CHRISTMASS',
    associated: 1,
    validated: 1,
    promoName: 'Test Promo 1',
    beginDate: '2020-11-26',
    endingDate: '2020-11-29' },
  { IDCampaign: 1,
    IDPromo: 107,
    promoType: 'CHRISTMASS',
    associated: 1,
    validated: 0,
    promoName: 'Test Promo 2',
    beginDate: '2019-12-21',
    endingDate: '2019-12-23' } ]

有什么想法吗?从我的立场来看,我没有做错任何事,但这不是我第一次错过明显的东西。

PS:请忽略我的广告系列名为“Tarzan”的事实。

你没有分享违规代码,所以我会给出一个通用的答案。

当症状是一堆所有对象似乎都有一个 属性,即 相同的项目数组,这很可能是由 正是。每个对象可能共享同一个数组实例。

典型的错误如下:

var items = [];
var containers = [];
for (let i = 0; i < 3; ++i) {
  items.push(i);
  let container = {};
  container.items = items;
  containers.push(container);
}

console.log(containers);

尽管人们可能期望甚至打算得到 3 个像这样的对象:

[
  { items: [ 0 ] },
  { items: [ 0, 1 ] },
  { items: [ 0, 1, 2 ] }
]

items数组实际上是一个数组的同一个实例。而你实际上得到的更像是:

[
  { items: [ 0, 1, 2 ] },
  { items: [ 0, 1, 2 ] },
  { items: [ 0, 1, 2 ] }
]

事实上,堆栈代码片段可视化工具在可视化方面做得更好,因为它输出:

[
  {
    "items": [
      /**id:3**/
      0,
      1,
      2
    ]
  },
  {
    "items": /**ref:3**/
  },
  {
    "items": /**ref:3**/
  }
]

通过这种方式,它试图通过给它一个 ref:3 标签并只是标记其他属性的值来告诉你它实际上是 同一个数组 使用与评论相同的 ref:3 标签。

我看到的原因通常源于对将数组分配给 属性 的含义的误解。这样做 不会 创建数组的副本。两个对象都引用 相同的数组

这可能很奇怪,因为:它在调试器中看起来是正确的,方法是在您单步执行循环 时检查数组的内容。即使我们在循环中添加了 console.log(items) 甚至 console.log(container) 之类的日志消息,我们可能仍然没有足够的线索来判断出了什么问题,因为那个数组实例的内容随着每个实例的变化而变化循环的迭代,我们可以将它的版本转储为看起来正确的文本,但随后在下一次迭代中无意中更改了数组的内容。

但是如果我们在 循环之后记录整个 containers 数组 ,您会发现每个对象都有相同的数组实例。如果只有一个对象获得分配给它的数组,那么分配一个数组还不错,但是如果你将同一个数组分配给多个对象的属性,或者在一个循环中,你可能 运行 进入这个问题。

您可以尝试的一种 habit-breaker 方法是在主循环之后的 第二个循环 中检查 所有 对象,而不是将您的日志记录代码直接潜入第一个循环。它的效率较低,但如果您经常犯此类错误,它可以帮助您发现问题并实现正确。

另一个打破习惯的是 console.log(JSON.stringify(foo)) 而不是 console.log(foo) 因为浏览器中的 console.log 实际上会记住对象的引用并向您显示它的当前内容,而不是它在它被记录的时间。这取决于平台,其中 node.js 将记录为文本而不是引用。

祝你好运,小心!