Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to clone an object with an arrow function in JavaScript?

I have this snippet of JavaScript code:

class Foo {
    constructor() {
        this.b = 1;
        this.getB = () => { return this.b; };
    }
}

const normalFoo = new Foo();
const clonedFoo = magicClone(normalFoo);

clonedFoo.b = 5;

console.log(clonedFoo instanceof Foo); // should be true
console.log(clonedFoo.getB()); // should be 5

I'd like to know what I can replace magicClone with in order to get the desired result (e.g. a clone that respects arrow function binding).

I'm fine with any sorts of terrible hacks and I'm also fine with solutions that work most of the time, as long as they work in this case. This is mostly for my edification :)


Please don't close this question as a duplicate - cloning an object has been asked many times, but I couldn't find a single answer that does this. Object.assign, lodash's cloneDeep, jQuery's clone, etc all do not handle this case.

like image 773
thedayturns Avatar asked Aug 20 '18 23:08

thedayturns


People also ask

What does () => mean in JavaScript?

It's a new feature that introduced in ES6 and is called arrow function. The left part denotes the input of a function and the right part the output of that function.

How do you return an object from arrow?

The most common and standard way of returning an object from an arrow function would be to use the longform syntax: const createMilkshake = (name) => { return { name, price: 499 }; }; const raspberry = createMilkshake('Raspberry'); // 'Raspberry' console. log(raspberry.name);

What does => mean in programming?

What It Is. This is an arrow function. Arrow functions are a short syntax, introduced by ECMAscript 6, that can be used similarly to the way you would use function expressions. In other words, you can often use them in place of expressions like function (foo) {...} .


2 Answers

So the main challenge here is that you have an arrow function that is assigned as a property on your Foo instances. Since arrow functions inherit their this from their enclosing context, and they can't be rebound once created, the only way to have getB reference the clone's b field is to recreate that arrow function. That means you have to somehow invoke Foo's constructor so that the arrow function will be recreated with the right context.

That said, this magicClone implementation will do the trick for this example:

function magicClone(obj) {
    // Manually create new instance of whatever `obj` is by invoking its constructor:
    const newInstance = new obj.__proto__.constructor()

    // Assign to the new instance all the non-function properties of `obj`.
    Object.assign(newInstance, JSON.parse(JSON.stringify(obj)));

    return newInstance;
}

However, the main drawback of this approach is that if obj's constructor requires any arguments, there is no way you can know what they should be. So this approach relies on the fact that your example Foo class has an argumentless constructor. But if you can stay within that limititation, it does get you the right output:

class Foo {
    constructor() {
        this.b = 1;
        this.getB = () => { return this.b; };
    }
}

function magicClone(obj) {
    const newInstance = new obj.__proto__.constructor()
    Object.assign(newInstance, JSON.parse(JSON.stringify(obj)));
    return newInstance;
}

const normalFoo = new Foo();
normalFoo.otherProp = "must stay the same";

const clonedFoo = magicClone(normalFoo);
clonedFoo.b = 5;

console.log(clonedFoo instanceof Foo); // should be true
console.log(clonedFoo.getB()); // should be 5
console.log(clonedFoo.otherProp)
like image 191
CRice Avatar answered Oct 05 '22 23:10

CRice


It's fundamentally impossible to clone a function. It might be a closure, and we can neither know nor clone what it closes over. (Arrow functions closing over their this value is just a special case of this).

Your best bet will be having a cloning protocol implemented by your instances:

class Foo {
    constructor() {
        this.b = 1;
        this.getB = () => { return this.b; };
    }
    clone() {
        return new Foo // usually passing the current instance's state as arguments
    }
}
function magicClone(o) {
    if (Object(o) !== o) return o; // primitive value
    if (typeof o.clone == "function") return o.clone(); // if available use it
    return Object.assign(Object.create(Object.getPrototypeOf(o)), o); // shallow copy
}

const normalFoo = new Foo();
const clonedFoo = magicClone(normalFoo);
like image 43
Bergi Avatar answered Oct 05 '22 22:10

Bergi