Javascript:我正在更新对象键的值。但是更新后另一个密钥对也改变了价值

Javascript: I am updating the value of an object key. BUT after the update another key pair also changes value

我正在为发票程序开发 API。我的问题是,当我尝试构造一个 json 并更新一些键时,一个键在我没有更新的情况下发生了变化。

这是一个没有所有 api 调用的 MRE。我只是将准备好的对象用于数据。

const business = {
   vatNumber: '050112718',
   country: 'GR',
   branch: 0
}

const customer = {
   vatNumber: '050112915',
   country: 'GR',
   branch: 0,
   postalCode: 28100,
   city: 'Argostoli'
}

const invoiceProperties = {
   series: 0,
   aa: 1,
   issueDate: '2021-12-15',
   type: '1_1',
   currency: 'EUR'
}

const payment = {
   type: 3
}

const product = {
   price: 10,  
   taxCode: 1,    
   classificationType: 'type a',
   classificationCategory: 'category 1',
}

const lines = [
   {
      lineNumber: 1,  
      netValue: 0.00,
      vatCategory: 0,
      vatAmount: 0,      
      incomeClassification: {
        'icls:classificationType': '',
        'icls:classificationCategory': '',
        'icls:amount': 0.00
      }

   },   
   {
      lineNumber: 2,  
      netValue: 0.00,
      vatCategory: 0,
      vatAmount: 0,      
      incomeClassification: {
        'icls:classificationType': '',
        'icls:classificationCategory': '',
        'icls:amount': 0.00
      }

   },   
   {
      lineNumber: 3,  
      netValue: 0.00,
      vatCategory: 0,
      vatAmount: 0,      
      incomeClassification: {
        'icls:classificationType': '',
        'icls:classificationCategory': '',
        'icls:amount': 0.00
      }

   }   
]

// json invoice creation
const invoiceObj = {
   
   invoice: {
      issuer: {
         vatNumber: business.vatNumber,
         country: business.country,
         branch: business.branch
      },
      
      counterpart: {
         vatNumber: customer.vatNumber,
         country: customer.country,
         branch: customer.branch,
         address: {
            postalCode: customer.postalCode,
            city: customer.city
         }
      },
      
      invoiceHeader: {
         series: invoiceProperties.series,
         aa: invoiceProperties.aa,
         issueDate: invoiceProperties.issueDate,
         invoiceType: invoiceProperties.type,
         currency: invoiceProperties.currency
      },
      
      paymentMethods: {
         paymentMethodDetails: {
            type: payment.type,
            amount: ""
         }
      },
      
      invoiceDetails: [],     
      
      invoiceSummary: {
         totalNetValue: 0,
         totalVatAmount: 0,
         totalWithheldAmount: '0.00',
         totalFeμesAmount: '0.00',
         totalStampDutyAmount: '0.00',
         totalOtherTaxesAmount: '0.00',
         totalDeductionsAmount: '0.00',
         totalGrossValue: 0,
         incomeClassification: []
      }
   }
}

//making paths into object more simle
const invoiceDetails = invoiceObj.invoice.invoiceDetails;
const summaryClassification = invoiceObj.invoice.invoiceSummary.incomeClassification;

const makeLines = ()=> {

   for (const line of lines) {
        
      line.netValue = product.price.toFixed(2);
      line.vatCategory = product.taxCode;
      line.incomeClassification["icls:classificationType"] = product.classificationType;
      line.incomeClassification["icls:classificationCategory"] = product.classificationCategory;
      line.incomeClassification["icls:amount"] = product.price.toFixed(2);
      
      switch (line.vatCategory) {
         case 1:
         line.vatAmount = (line.netValue * 0.24).toFixed(2);
         break;
         case 2:
         line.vatAmount = (line.netValue * 0.17).toFixed(2);
         break;
         case 3:
         line.vatAmount = (line.netValue * 0.13).toFixed(2);
         break;
         case 4:
         line.vatAmount = (line.netValue * 0.09).toFixed(2);
         break;
         case 5:
         line.vatAmount = (line.netValue * 0.06).toFixed(2);
         break;
         case 6:
         line.vatAmount = (line.netValue * 0.04).toFixed(2);
         break;
         case 7:
         line.vatAmount = '0.00';
         break;
         case 8:
         line.vatAmount = '0.00';
         break;
      };
      
      console.log(line); //just to see something in dev mode

      invoiceObj.invoice.invoiceSummary.totalNetValue = (invoiceObj.invoice.invoiceSummary.totalNetValue/1 + line.netValue/1).toFixed(2);
      invoiceObj.invoice.invoiceSummary.totalVatAmount = (invoiceObj.invoice.invoiceSummary.totalVatAmount/1 + line.vatAmount/1).toFixed(2);
      invoiceObj.invoice.invoiceSummary.totalGrossValue = (invoiceObj.invoice.invoiceSummary.totalGrossValue/1 + line.netValue/1 + line.vatAmount/1).toFixed(2);

      const lineClassification = line.incomeClassification;
      const lineType = lineClassification["icls:classificationType"];
      const lineCategory = lineClassification["icls:classificationCategory"];

      const classificationIndex = summaryClassification.findIndex(o => o["icls:classificationType"] === lineType && o["icls:classificationCategory"] === lineCategory);

         
      // if there isn't a array item with the same combination of categories, we add the classifications object
      if (classificationIndex < 0) { 
         summaryClassification.push(lineClassification);   
      } else {  // else if there is a an item in the array with the same categories, we add the amount of the item to the total
         summaryClassification[classificationIndex]["icls:amount"] = (summaryClassification[classificationIndex]["icls:amount"]/1 + line.netValue/1 ).toFixed(2);
      }

      invoiceDetails.push(line);
   }

}

我的问题

else 语句以某种方式改变了值 os 第一行的 incomeClassification。 +

鉴于从第一次迭代开始,该键应该是“10.00”,紧随代码 line.incomeClassification["icls:amount"] = product.price.toFixed(2); 和 product.price beeing = 10。

此外,显然 else 语句代码更新了摘要属性而不是行属性 summaryClassification[classificationIndex]["icls:amount"] = (summaryClassification[classificationIndex]["icls:amount"]/1 + line.netValue/1 ).toFixed(2);

如果我在 makeLines() 的和处注释掉 else 语句:

也许还有另一种方法可以做到这一点,但为什么首先会发生这种情况?

任何帮助将不胜感激...

解决眼前的问题:每个 line 都有一个 lineClassification 道具,它本身就是一个对象。随着 lines 被迭代,其中一些 lineClassification 被放置在另一个数组 (summaryClassification) 中。外循环的后续轮次是查找数组的元素并改变它们,但这些 lineClassifications 是 lines.

指向的相同对象

这里有一个更简单的 MRE 来说明...

const lines = [
  { name: 'line0', classificationObject: { name: 'class0', number: 10 } },
  { name: 'line1', classificationObject: { name: 'class1', number: 10 } },
  { name: 'line2', classificationObject: { name: 'class0', number: 10 } },
]

let summary = []

for (line of lines) {
  let classificationObject = line.classificationObject;
  let classificationName = classificationObject.name
  let index = summary.findIndex(s => s.name === classificationName);
  if (index == -1) {
    summary.push(classificationObject); // the problem is here.
    // the SAME classification object now appears in TWO places:
    // in the line object, and in the summary array
    // future mutations will be seen by both
  } else {
    // as a result, this line seems to modify two objects
    // it's really modifying one object, pointed to from multiple places
    summary[index].number += 12
  }
}

console.log("logging the summary, it's what we expect. The saved class0 classification has been mutated:")
console.log(summary)

console.log("But logging the lines, we're surprised to find line 0's classification object has been mutated. it happens because that same object was also placed in the summary array, and was mutated in a subequent turn of the loop:")
console.log(lines)

修复 MRE:

显然 summaryClassification 数组必须被赋予 lineClassification 对象的副本,一个可以独立于 line它曾经属于。

const lines = [
  { name: 'line0', classificationObject: { name: 'class0', number: 10 } },
  { name: 'line1', classificationObject: { name: 'class1', number: 10 } },
  { name: 'line2', classificationObject: { name: 'class0', number: 10 } },
]

let summary = []

for (line of lines) {
  // do some stuff that doesn't matter
  let classificationObject = line.classificationObject;
  let classificationName = classificationObject.name
  let index = summary.findIndex(s => s.name === classificationName);
  if (index == -1) {
    let classificationCopy = Object.assign({}, classificationObject);  // see here: make a copy
    summary.push(classificationCopy); // subsequent turns will
    // mutate the copy, leaving the original line alone
  } else {
    summary[index].number += 12
  }
}

console.log(summary)
console.log("logging the lines, see what we expect in line 0, an unchanged classification object")
console.log(lines)

OP 术语:

  if (classificationIndex < 0) { 
     let lineClassificationCopy = Object.assign({}, lineClassification);  // see here: make a copy
     summaryClassification.push(lineClassificationCopy);   
  } else {  // else if there is a an item in the array with the same categories, we add the amount of the item to the total
     summaryClassification[classificationIndex]["icls:amount"] = (summaryClassification[classificationIndex]["icls:amount"]/1 + line.netValue/1 ).toFixed(2);
  }

顺便说一句,代码将数值表示为字符串。更好的做法是将数字表示为数字,对数字进行数学运算,将中间结果存储为数字等,然后将数字转换为字符串 仅当 必须将它们呈现给用户时。对于货币,这应该使用区域设置敏感格式来完成。

此外,该代码似乎旨在使用不够强大的技术进行现实生活中的复杂业务计算。如果这是一个生产系统,我担心最终的所有者会发现它不透明且无法维护。我建议在下一次修订中审查和使用 object oriented concepts