Factory functions prototype

Viewed 503

Learning constructors I came with this question:

Why not to use just normal functions returning an object instead of constructors?

Reading a bit, I found about factory functions (ff). The differences with constructors are:

  • The use of this that replaces the object literal { },
  • The use of new replacing the return of the object.

But that's not enough simplification I believe, it's just a bit tidier, but not much.

Take this example:

function box(x,y,z){
return { 
        x:x , y:y , z:z , 
        volume: function (){return x*y*z} 
        }
   }

We can easily write a constructor with same function.

Now, I know one of the differences might be the prototype. So here is my question:

  • What are the key differences between ff and constructors? Is the prototype a difference between factory functions and constructors?
2 Answers

The prototype object that is created together with the function is indeed making the difference. When you call the function with new, the this object will get that prototype object as its prototype. So the prototype chain for that new object is one step longer than what you get with the factory function.

instanceof

The language offers the instanceof operator and the constructor property, which are useful when you use constructors, as they give information about the instance's constructor. In the case of an object literal (as used in your box function), that determines that the constructor is Object and not box.

To bring the two versions of the functions a bit closer together, so that only the essential differences are highlighted, you could write them as follows:

function box(x,y,z) {
    let obj = {};
    obj.x = x;
    obj.y = y;
    obj.z = z;
    // Better reference the properties here, ...in case they are changed:
    obj.volume = function () { return obj.x * obj.y * obj.z } 
    return obj;
}

The constructor could look like this:

function Box(x,y,z) {
    // `this` is already initialised as a new object with a specific prototype
    this.x = x;
    this.y = y;
    this.z = z;
    this.volume = function () { return this.x * this.y * this.z }
    // `this` is returned by default, so no `return` is needed
}

Notice the difference in the prototype chains (run the snippet) and the usefulness of instanceof:

function box(x,y,z) {
    let obj = {};
    obj.x = x;
    obj.y = y;
    obj.z = z;
    obj.volume = function () { return obj.x * obj.y * obj.z } 
    return obj;
}

function Box(x,y,z) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.volume = function () { return this.x * this.y * this.z }
}

let a = box(1,2,3);
let b = new Box(1,2,3);

// Print prototype chains
console.log("a-->" + Object.getPrototypeOf(a).constructor.name);

let protB = Object.getPrototypeOf(b);
console.log("b-->" + protB.constructor.name + "-->" + 
                     Object.getPrototypeOf(protB).constructor.name);

// Print use of `instanceOf`
console.log("a instanceof box?", a instanceof box);
console.log("b instanceof Box?", b instanceof Box);

Methods

So far I ignored the fact that the volume function could be reused instead of defined separately for each instance. You can in both cases define the volume function as a (more) global function. Note that you now could use the this keyword, since normally you would call volume as instance.volume():

function volume { return this.x * this.y * this.z }

function box(x,y,z) {
    let obj = {};
    obj.x = x;
    obj.y = y;
    obj.z = z;
    obj.volume = volume; 
    return obj;
}

Still, you have the explicit obj.volume = volume assignment. In the constructor pattern, you don't need to create that instance property. It is enough the define the method on the prototype object:

function Box(x,y,z) {
    this.x = x;
    this.y = y;
    this.z = z;
    // No need here to define `volume`
}

Box.prototype.volume = function { return this.x * this.y * this.z };

So, actually you only have one reference to the method.

Class syntax

Finally, there is the class syntax, which is an alternative to use constructors:

class Box(x,y,z) {
    constructor(x,y,z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    volume() {
        return this.x * this.y * this.z;
    }
}

In this syntax volume is also defined on the prototype, not on the instance.

Downsides to using constructors

So far, the comparison was favourable to using constructors (in my opinion).

But:

  • The use of the this object can be avoided completely by using factory functions. Using this can be confusing (as the many questions on that topic on Stack Overflow demonstrate). Some coders feel this is good reason to abandon constructors.
  • Creating longer prototype chains can impact performance negatively and sometimes make simple tasks look complicated. For the above example there is no such problem, but the typical examples for using Object Oriented Programming, where you have a Being from which Animal is derived, from which Mammal is derived, from which ...etc, etc, can get confusing. A flat data structure can then feel like a relief.

YES, Prototype is a difference between the so called factory functions and constructors.

In the sample code that you had given, you won't be able to access the prototype of box, even if that is added. When you call a function with new keyword, the first job that it would do internally is to create an object and create its prototype chain based on the references that you given via box.prototype and after setting that up the process will return the object created. But here in your code, the access has been restricted to the object that you are returning and it will override the return of the originally created object.

Related