javascriptarraysobjectjavascript-objectsarrayobject

Is there a way to copy an array of objects in javascript?


This may be an issue caused by my incompetence in javascript as I am new to the language but so far I haven't found a working solution.

The problem is that I want to make a copy array of an array of objects, then change the data in the copy array without changing the values of the objects in the initial array and finally assign the copy array to the initial array again so I can use it again in another function with the initial values.

I have made a simple program containing two classes - Country which holds some properties and a function to change one of these properties and World which hold an array of countries and a function to change the data of all countries in the array. I create a World instance, call the changeAllCountriesData function and at the end print the values of both the countries array and the copyData array. As you can see they have both changed and I am unsure of the reason why.

class Country{
    name; area; population;

    constructor(name, area, population){
        this.name = name;
        this.area = area;
        this.population = population;
    }

    changeData(){
        this.population += Math.round(this.population * 0.02);
    }
}

class World{
    countries = [];
    copyData = [];

    constructor(){
        this.loadData();
        this.copyData = this.countries.slice(0);
    }

    loadData(){
        this.countries.push(new Country("Denmark", 41990, 5839809));
        this.countries.push(new Country("Germany", 349360, 83159604));
        this.countries.push(new Country("Netherlands", 33690, 17342709));
    }

    changeAllCountriesData(){
        this.copyData.forEach(c => {
            c.changeData();
        })
    }
}
console.log("Initial information for the population: 5839809, 83159604, 17342709")
w = new World();
w.changeAllCountriesData();
console.log("countries array:")
console.log(w.countries);
console.log("copyData array:")
console.log(w.copyData);

Other ways I have tried instead of this.copyData = this.countries.slice(0):

1.

this.copyData = [...this.countries]

2.

this.copyArray = Array.from(this.countries);
this.copyData = copyArray[0];

3.

this.countries.forEach(c => {
      this.copyData.push({...c});
})

The one above returns NaN after each calculation.

4.

this.countries.forEach(c => {
      let obj = Object.assign({}, c);
      this.copyData.push(obj);
})

5.

this.copyData = this.countries.concat();

None of these methods has made a copy array in such a way that the initial one remains unchanged or the calculations are made without returning NaN.


Solution

  • In JS, objects are copied using reference. So when you so .slice(0) you are partially correct but the issue is, internal values are objects. So the new array has previous values.

    Now you can also try .map(obj => ({ ...obj }) ) but this will not work as object spread will create a new object with properties but not methods. So any method, like changeData will not be there on this object. Due to this, you will have to create a copy mechanism to your class.

    Try updating to

    cloneObject() {
      const {name, area, population} = this;
      return new Country(name, area, population)
    }
    
    ...
    
    this.copyData = this.countries.map( (obj) => obj.cloneObject() );
    

    Note: You can extend this function's capability. You can add an argument to override the values. That will allow you to do multiple operations in one go:

    cloneObject( overrides ) {
      const { name, area, population } = { ...this, ...overrides };
      return new Country(name, area, population)
    }
    
    ...
    
    this.copyData = this.countries.map( (obj) => obj.cloneObject({ name: 'Hello World' }) );
    

    Sample Code:

    class Country{
        name; area; population;
    
        constructor(name, area, population){
            this.name = name;
            this.area = area;
            this.population = population;
        }
        
        cloneObject() {
          const {name, area, population} = this;
          return new Country(name, area, population)
        }
    
        changeData(){
            this.population += Math.round(this.population * 0.02);
        }
    }
    
    class World{
        countries = [];
        copyData = [];
    
        constructor(){
            this.loadData();
            this.copyData = this.countries.map( (obj) => obj.cloneObject() );
        }
    
        loadData(){
            this.countries.push(new Country("Denmark", 41990, 5839809));
            this.countries.push(new Country("Germany", 349360, 83159604));
            this.countries.push(new Country("Netherlands", 33690, 17342709));
        }
    
        changeAllCountriesData(){
            this.copyData.forEach(c => {
                c.changeData();
            })
        }
    }
    console.log("Initial information for the population: 5839809, 83159604, 17342709")
    w = new World();
    w.changeAllCountriesData();
    console.log("countries array:")
    console.log(w.countries);
    console.log("copyData array:")
    console.log(w.copyData);