I am pretty confused what happens with my code.
The tool I wrote, accepts uploaded CSV files and parses some to get some data.
To have a init state for my useres I globally declared emptyUser
with default values.
When I run my script and upload a file, emptyUser
gets updated with data, which should never happen.
When I do not process any data, but output the emptyUser
straight away, the object is empty as expected.
The 2nd thing is, that all users have the same data at the end, which is also the same data as in emptyUser
Could someone explain to me why the object gets filled even when I never change the content of it and why all users have the same content as well?
import React from "react";
import {DropzoneArea} from "material-ui-dropzone";
let emptyUser = {
dealt: {
totalHull: 0,
avgHull: 0,
totalShield: 0,
avgShield: 0,
totalMitigated: 0,
avgMitigated: 0,
totalDealt: 0,
avgDealt: 0,
highest: 0,
lowest: 9999999999999999999999999999,
attackCount: 0,
critCount: 0,
highestCrit: 0,
lowestCrit: 0
},
received: {
totalHull: 0,
avgHull: 0,
totalShield: 0,
avgShield: 0,
totalMitigated: 0,
avgMitigated: 0,
totalDealt: 0,
avgDealt: 0,
highest: 0,
lowest: 9999999999999999999999999999,
attackCount: 0,
critCount: 0,
},
roundDied: 0
}
let attackerList = [];
let users = [];
export class App extends React.Component {
roundTwoDecimals(num) {
return Math.round((num Number.EPSILON) * 100) / 100
}
prepareUsers(actions) {
let users = [];
actions.forEach(action => {
let attackerType = action['Angreiferallianz'].trim() === '--' ? 'hostile' : 'user';
let attacker = action['Angreifername'];
if (action['Typ'] === 'Angriff' && attackerType === 'user' && !attackerList.includes(attacker)) {
// console.log('empty',emptyUser); // even this was full of data
users[attacker] = emptyUser;
attackerList.push(attacker);
}
})
return users;
}
calcBattleData(data, receivedOrDealt, hullDamage, shieldDamage, mitigatedDamage, dealtDamage, isCrit) {
data[receivedOrDealt] = {
...data[receivedOrDealt],
attackCount: data[receivedOrDealt].attackCount 1,
totalHull: data[receivedOrDealt].totalHull hullDamage,
totalShield: data[receivedOrDealt].totalShield shieldDamage,
totalMitigated: data[receivedOrDealt].totalMitigated mitigatedDamage,
totalDealt: data[receivedOrDealt].totalDealt dealtDamage,
highest: data[receivedOrDealt].highest <= dealtDamage ? dealtDamage : data[receivedOrDealt].highest,
lowest: data[receivedOrDealt].lowest >= dealtDamage && dealtDamage > 0 ? dealtDamage : data[receivedOrDealt].lowest,
critCount: isCrit ? data[receivedOrDealt].critCount 1 : data[receivedOrDealt].critCount
};
return data;
}
parseLog(actions) {
// let users = this.prepareUsers(actions); // commented out because I suspected that something in this function caused it
let target = emptyUser;
let count = 0;
actions.some(action => {
if (action['Typ'] === 'Angriff') {
let attacker = action['Angreifername'];
let attacked = action['Zielname'];
let attackerType = action['Angreiferallianz'].trim() === '--' ? 'hostile' : 'user';
let hullDamage = parseInt(action['Hüllenschaden']);
let shieldDamage = parseInt(action['Schildschaden']);
let mitigatedDamage = parseInt(action['Abgemilderter Schaden']);
let dealtDamage = parseInt(action['Gesamtschaden']);
let isCrit = action['Kritischer Treffer?'] === 'JA';
// the attacker is a player - alternative for this.prepareUsers
if(attackerType === 'user' && !attackerList.includes(attacker)){
users[attacker] = emptyUser;
attackerList.push(attacker);
}
// the attacked one is a player - alternative for this.prepareUsers
if(attackerType === 'hostile' && !attackerList.includes(attacked)){
users[attacked] = emptyUser;
attackerList.push(attacked);
}
if (attackerType === 'user') {
users[attacker] = this.calcBattleData(users[attacker], 'dealt', hullDamage, shieldDamage, mitigatedDamage, dealtDamage, isCrit);
target = this.calcBattleData(target , 'received', hullDamage, shieldDamage, mitigatedDamage, dealtDamage, isCrit);
} else {
users[attacked] = this.calcBattleData(users[attacked], 'received', hullDamage, shieldDamage, mitigatedDamage, dealtDamage, isCrit);
target = this.calcBattleData(target , 'dealt', hullDamage, shieldDamage, mitigatedDamage, dealtDamage, isCrit);
}
}
// Kämpfer vernichtet
count ;
// limit it to 20 actions for debugging
if(count === 20){
return true;
}
})
Object.keys(users).forEach(user => {
users[user].dealt.avgHull = this.roundTwoDecimals(users[user].dealt.totalHull / users[user].dealt.attackCount);
users[user].dealt.avgDealt = this.roundTwoDecimals(users[user].dealt.totalDealt / users[user].dealt.attackCount);
users[user].dealt.avgShield = this.roundTwoDecimals(users[user].dealt.totalShield / users[user].dealt.attackCount);
users[user].dealt.avgMitigated = this.roundTwoDecimals(users[user].dealt.totalMitigated / users[user].dealt.attackCount);
});
console.log(users);
console.log(target);
}
handleFileUpload(files) {
if (files && files.length <= 0) {
return false;
}
files.forEach(file => {
let myFile = file;
let reader = new FileReader();
reader.readAsText(myFile, 'UTF-8');
reader.addEventListener('load', e => {
console.log(e);
let csvData = e.target.result;
csvData = csvData.replaceAll(/Officer,(\d*),(\d*),(\w)/g, 'Officer,$1.$2,$3');
let csvBlocks = csvData.split("\r\n\r\n");
csvBlocks.forEach((block, index) => {
//console.log(block);
if (block === '') return false;
let lines = block.split(/\n/g);
let headers = lines[0].split(',')
let result = [];
for (let i = 1; i < lines.length; i ) {
let obj = {};
let currentLine = lines[i].split(",");
for (let j = 0; j < headers.length; j ) {
obj[headers[j]] = currentLine[j];
}
result.push(obj);
}
// process only the fourth block for now
if (index === 3) {
console.log('parse log');
console.log(emptyUser); // already here the object is filled, when I keep in the next line
this.parseLog(result); // when I comment this out, empty user has the correct content
}
})
});
})
}
render() {
return (
<>
<DropzoneArea
onChange={(files) => this.handleFileUpload(files)}
/>
</>
)
}
}
CodePudding user response:
Recall that in JavaScript, objects are passed by reference. Quoting MDN docs,
Objects are a reference type. Two distinct objects are never equal, even if they have the same properties.
Whenever you push an object to an array, you're actually pushing its reference and any changes to one of its properties will propagate to all occurrences of that object in the array.
In this case, the same emptyUser
is being added to users
array in some users[attacker] = emptyUser
lines. The immediate fix is to instantiate a new object each time you use emptyUser
. Luckily, with the spread syntax (...)
, you're able to perform a copy of all the properties onto the new reference.
It might be a better way of doing it in terms of refactoring, but changing all the occurrences of raw emptyUser
to users[attacker] = {...emptyUser}
should solve the problem right off the bat.