JavaScript constructor

If you have a background in strong-typed programming languages (as I have, incidentally), the JavaScript concept of class could look fuzzier than expected.

Here I show a standard way of defining a class, with a variation.

You are need in your code a lot of rectangles. In C++, Java(, etc), you would define a class Rectangle, and put in it all the relevant data and method. The JavaScript approach is different: you define a function where you create its associated data properties, using the parameter passed by the user, and let the function inherit from its prototype all the other properties you need.

This function smells like a constructor, acts like it, so it is actually called constructor, and typically given a name starting with an uppercase letter to visually show that here there is something special. But remember that, from the JavaScript point of view, it is just a plain function. The magic is done when we call it through the "new" operator. It is its job to perform the association between the constructor and its prototype, and make the constructor return by default its "this" pointer.

Here is an example where I define, instantiate, and use a Rectangle:
function Rectangle(a, b) {
    this.a = a;
    this.b = b;
}

Rectangle.prototype = {
    constructor: Rectangle, // 1
    toString: function() { return "a = " + this.a + ", b = " + this.b; }, // 2
    area: function() { return this.a * this.b; },
    perimeter: function() { return 2 * (this.a + this.b); }
}
    
var r = new Rectangle(4, 10); // 3
console.log("rectangle: ", r.toString()); // 4
console.log("area: ", r.area());
console.log("perimeter: ", r.perimeter());
console.log("Is r a rectangle? ", r instanceof Rectangle); // 5
console.log("Rectangle constructor: ", Rectangle.prototype.constructor); // 6
1. Backlink from the Rectangle prototype and its constructor.
2. All the JavaScript objects have a toString() method, here we redefine it to give some useful information to this specific type.
3. This is the core of the story: we call Rectangle through the "new" operator. If we forget to do it, we do not create a new object, we simply call a function that creates a couple of properties in the global scope and returns an undefined value.
4. Now we can use r as a Rectangle object.
5. Notice that the instanceof operator performs a check of the object against a constructor. Actually it won't check if the object has been built through that constructor, but if it inherits from the constructor's prototype.
6. If you comment out the line (1), you'll see here why we need it. Defining a function prototype as showed, leads to overwriting the default constructor property, breaking the backlink to that function. So we have to reset it.

To avoid the nuisance of (6) we could create just the properties we really need, and not redefine the complete prototype:
Rectangle.prototype.area = function() { return this.a * this.b; };
Rectangle.prototype.perimeter = function() { return 2 * (this.a + this.b); };
Rectangle.prototype.toString = function() { return "a = " + this.a + ", b = " + this.b; };
Choose whichever approach you think is more suitable to you.

The weakest point in this design is in (3). Rectangle is just a function, calling it directly is not an error, from the JavaScript perspective, even if it almost ever it doesn't make much sense and leads to a disaster.

Using the factory pattern our code would be stronger, as I am going to show in the next post.

No comments:

Post a Comment