2

I have two objects that are the same type and I want to copy the content of one of them to the other.

const Invoice1 = new InvoiceModel(); 

const Invoice2 = new InvoiceModel();

now in order to have something like : Invoice2 = Invoice1

After reading :

How do I correctly clone a JavaScript object?

I tried to use any of below commands but all of them say that invoice2 is not defined at runtime:

 Invoice2 = { ...Invoice1 };  //OR

 Invoice2 = Object.assign({}, Invoice1);  //OR

 Invoice2 = JSON.parse(JSON.stringify(Invoice1));

finally I used this function to copy the content of objects by reading this article (https://medium.com/@Farzad_YZ/3-ways-to-clone-objects-in-javascript-f752d148054d):

function CopyObject(src, target) {
  for (let prop in src) {
    if (src.hasOwnProperty(prop)) {
      target[prop] = src[prop];
    }
  }
  return target;
}

I wonder is there any cleaner way to do that except using above function?

I have read many post regarding this issue but all of them create a new object.

albert sh
  • 1,095
  • 2
  • 14
  • 31
  • i always use the spread operator `...` – messerbill May 28 '19 at 18:21
  • 2
    `Object.assign(Invoice2, Invoice1)` is the modern way – danh May 28 '19 at 18:23
  • @danh you will need to use some polyfills because `Object.assign` is not supported in IE – messerbill May 28 '19 at 18:24
  • You can use first two method only for shallow copies, you need to use 3rd one in case you want to do deep clone, there are many libraries which you can use if you want such as loadash – Code Maniac May 28 '19 at 18:25
  • @CodeManiac due to there is no recursion this cannot be a real deep copy, can it? – messerbill May 28 '19 at 18:27
  • Possible duplicate of [How to clone a javascript ES6 class instance](https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance) – Michael Sorensen May 28 '19 at 18:27
  • @messerbill, what is the command as Invoice2 = { ...Invoice1 }; doesnt work for existing object and says invoice2 is not defined – albert sh May 28 '19 at 18:28
  • @messerbill if your write recursive function than you can create deep clone too, but it is same as reinventing wheel – Code Maniac May 28 '19 at 18:28
  • But how does it say *"invoice2 is not defined*" at runtime? Have you not defined it `const Invoice2 = new InvoiceModel()`? – adiga May 28 '19 at 18:29
  • @MichaelSorensen, your mentioned link and other ones creates a new object and doesnt work with existing object – albert sh May 28 '19 at 18:29
  • 1
    Because you can't reassign a const – JasonB May 28 '19 at 18:30
  • Possible duplicate of [How do I correctly clone a JavaScript object?](https://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object) – JasonB May 28 '19 at 18:30
  • @adiga, its in part of the code that may run in some circumstances. – albert sh May 28 '19 at 18:32
  • @CodeManiac why is this reinventing the wheel? I just think that the function the OP posted above does not do a real deep copy due to it is missing a recursive call – messerbill May 28 '19 at 18:34
  • I still don't understand. So you don't have the line `const Invoice2 = new InvoiceModel();`? Are you running `Invoice2 = { ...Invoice1 }; ` before the identifier `Invoice2` is created? This seems like an [XY problem](https://meta.stackexchange.com/questions/66377) – adiga May 28 '19 at 18:35
  • @adiga, No I have already defined Invoice1 and Invoice2 and then used spread operator. It just gives error at run time, even if I use Invoice2.Number=Invoice1.Number it works fine – albert sh May 28 '19 at 18:40
  • If you are going to reassign `Invoice2` maybe you should use `let Invoice2`? What is the compulsion to use `const` if you know you have to reassign. – adiga May 28 '19 at 18:42
  • @adiga, I cant use let because it has been defined in other places but even after changing it to var, I have the same issue – albert sh May 28 '19 at 18:47
  • *"I cant use let because it has been defined in other places"* What does this mean? If you change it `var` it should definitely allow you to reassign. You might not even need any of the deep cloning and such. The sole purpose of using `const` is to not allow further assignments. In all remaining situations `let` should be used – adiga May 28 '19 at 18:49
  • @danh, Thanks for your answer, your solution is the only one that works – albert sh May 30 '19 at 13:34

3 Answers3

7

I recommend creating a method in the prototype in InvoiceModel that does this automatically for you.

class InvoiceModel {
  constructor(num) {
    this.num = num
  }
  duplicate() {
    return Object.assign(Object.create(this), this)
  }
}

const obj1 = new InvoiceModel(10)
console.log(obj1.num)
const obj1Copy = obj1.duplicate()
console.log(obj1Copy.num)
console.log(obj1Copy === obj1)
Andrew
  • 7,201
  • 5
  • 25
  • 34
  • 2
    @albert sh - duplication will entail different things as soon as your classes stop representing simple data objects. – Jimmy Breck-McKye May 28 '19 at 18:52
  • 1
    @Andrew, Thanks for your answer, I still cant use duplicate to assign to an existing variable, if you already craete : obj1copy = new InvoiceMode, how can you use duplicate as Obj1 and Obj1Copy are both global variables? – albert sh May 28 '19 at 19:11
  • 1
    Assigning to existing variable names and creating copies of an existing object are two entirely different things. Maybe I am misinterpreting your question, but the reason why i `console.log(obj1Copy === obj1)` is to prove that they are separate entities. You can now modify one without affecting the other. – Andrew May 28 '19 at 19:39
  • @JimmyBreck-McKye Yea, that's true. But based on OPs example, this will accomplish the same thing, only cleaner, which is what was requested – Andrew May 28 '19 at 19:41
2

If the objects are just plain old data objects - with no methods or private state - you can just use a deep object clone method as specified here.

However, by the looks of things you are using classes with constructors, which implies you have methods and state. This is more tricky, because it suggests you may be relying on the constructor being re-run, e.g. to store private state in a closure, to allocate other resources, or rely on some kind of side effects. In that case you will need some kind of Invoice.prototype.clone method, that knows how to inject state into a new instance, and reruns the constructor function for the class - as per @andrew's answer.

I would avoid cloning objects with the syntax target = {...src}, which one commenter suggested. This will cause you trouble as soon as you have non-scalar reference members like sub-objects or arrays (as you will be copying pointers to the originals, not cloning their values). The same flaw applies to that CopyObject function you have picked up.

Jimmy Breck-McKye
  • 2,923
  • 1
  • 22
  • 32
0

I have implemented a deep copier of objects, it does not override anything in the target option, but if you need that, you can achieve that as well:

var defaults = {
        options: {
                remove: true,
                enable: false,
                instance: {}
        },

        log: {
                warn: true,
                error: true
        }
};

var config = {
        options: {
                remove: false,
                instance: null
        }
};

function applyDefaults(d, t) {
    if ((typeof d !== "object") && (typeof d !== "array")) {
        return d;
    }

    if (t === undefined) {
        if (typeof d === "object") {
            t = {};
        } else if (typeof d === "array") {
            t = [];
        }
    }

    for (var key in d) {
        if (t[key] === undefined) {
            t[key] = d[key];
        } else {
            applyDefaults(d[key], t[key]);
        }
    }
    return t;
}

applyDefaults(defaults, config);
console.log(config);

However, this will not copy "private" stuff, not defined as members of this.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175