Search code examples
javascriptprototypeprototype-programming

How to clone a constructor function so that it constructs a copy of the original type that behaves just like the original, but has its own prototype?


I am trying to clone a constructor function completely, so that it does exactly the same as the original but its prototype is set to a clone of the original.

I can easily clone the original prototype like so:

  // the original constructor function is assigned to the `type` variable.

    const prototype = type.prototype;
    const clonePrototype = Object.create(null);
    const props = Object.getOwnPropertyNames(prototype);
    for (let i = 0; i < props.length; ++i) {
        const propertyName = props[i];

        const propDescriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);

        Object.defineProperty(clonePrototype, propertyName,
              Object.getOwnPropertyDescriptor(prototype, propertyName));
    }

The issue I'm having is how to create a constructor function now that has its prototype property set to the clonePrototype object.

If I do:

    const newContructor = function() {};
    Object.setPrototypeOf(newContructor, clonePrototype);

then newConstructor ends up with __proto__ set to the clonePrototype object, and the prototype property unchanged. I'd like to leave __proto__ unchanged, and prototype set to the new value.

My goal is to clone this constructor function so that it constructs a copy of the original type that behaves just like the original, but has its own prototype.

How can I accomplish this?

Thanks!


Solution

  • I'm going to naively show you two solutions:

    1. Answers your "how to" question directly (per comments)
    2. The JavaScript way of producing the result

    I say naively, because perhaps there is some use case that requires the cloning to occur (perhaps some time after the Type prototype has changed over time). But, given your code examples, the JavaScript way presumes unmodified prototypes.

    So here is the first. Note the changes I made was to convert the function expression:

    const newContructor = function() {};

    To a function declaration:

    function NewConstructor() {};

    And since you Object.create'd the clone, I Object.assign'd it to the prototype.

    function Type() {};
    Type.prototype = {
      a: 1,
      b: 2,
      c: function() {
        console.log(this.a + " + " + this.b + " = " + (this.a + this.b));
      }
    }
    
    const prototype = Type.prototype;
    const clonePrototype = Object.create(null);
    const props = Object.getOwnPropertyNames(prototype);
    for (let i = 0; i < props.length; ++i) {
      const propertyName = props[i];
      const propDescriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);
      Object.defineProperty(clonePrototype, propertyName,
        Object.getOwnPropertyDescriptor(prototype, propertyName));
    }
    
    function NewConstructor() {};
    Object.assign(NewConstructor.prototype, clonePrototype);
    /********************************************/
    let type = new Type();
    const cloned = new NewConstructor();
    
    type.c(); // prints 1 + 2 = 3
    cloned.c(); // prints 1 + 2 = 3
    
    Object.getPrototypeOf(type).a = 0;
    // Alternative syntax that some will dislike and point out it should not be used 
    cloned.__proto__.a = 3;
    
    type.c(); // prints 0 + 2 = 2
    cloned.c(); // prints 3 + 2 = 5
    // Here are the independent prototypes (or type.__proto__ and cloned.__proto__
    console.log(Object.getPrototypeOf(type), Object.getPrototypeOf(cloned));

    If the prototype is a pristine prototype when the cloning occurs, it is much more direct to use the prototype as a constructor itself. This allows you to use the new operator with your prototype to construct a Singleton instance of the prototype itself.

    In this example you will see no cloning of the prototype. A singleton version of the prototype is created for each object that needs it as a prototype.

    The mutation of each prototype is then observed as independent from one another:

    function MyPrototype() {
      this.a = 1;
      this.b = 2;
      this.c = function() {
        console.log(this.a + " + " + this.b + " = " + (this.a + this.b));
      };
    }
    
    function Type() {};
    
    function NewConstructor() {};
    
    Type.prototype = new MyPrototype();
    NewConstructor.prototype = new MyPrototype();
    /********************************************/
    let type = new Type();
    const cloned = new NewConstructor();
    
    type.c(); // prints 1 + 2 = 3
    cloned.c(); // prints 1 + 2 = 3
    
    Object.getPrototypeOf(type).a = 0;
    // Alternative syntax that some will dislike and point out it should not be used 
    cloned.__proto__.a = 3;
    
    type.c(); // prints 0 + 2 = 2
    cloned.c(); // prints 3 + 2 = 5
    // Here are the independent prototypes (or type.__proto__ and cloned.__proto__
    console.log(Object.getPrototypeOf(type), Object.getPrototypeOf(cloned));