Thursday, 10 December 2015

ES6 Constructors

I've previously said here that I'm not particularly excited about the addition of classes to ES6, though well, as they are mainly syntactic sugar all the magic of Prototype based programming (or OLOO, Objects Linked to Other Objects) remains. Anyway, regardless of our predisposition to it, as they will become mainstream we'll have to end up using them, so I thought it was good to get used to them.

One feature in "classic JavaScript" that I pretty like is that we can decide in our "constructor functions" (those that we designed to be invoked with new) to return a different object, rather than the one that has been passed to it as "this". The main use that I see for it is for having a pool of objects, or an "implicit/transparent" singleton (that is indeed a particular case of an Object Pool). With this in mind I was wondering if ES6 class constructors would allow that. Hopefully yes. It's well explained in this post by Dr Rauschmayer. He calls it "Overriding the result of a constructor". I had not thought that the [Prototype] of a "class" points to the Parent "class", but it makes pretty sense in order to inherit static methods. This diagram shows the whole picture of how objects the class inheritance sugar is translated into the prototypal world.

The whole article is pretty interesting. Reading it I found out that classes are not just compile time sugar, but that their inclusion has indeed modified the way the runtime works, as the creation of the object passed as this to a (constructor) function is done differently:

The instance object is created in different locations in ES6 and ES5:

In ES6, it is created in the base constructor, the last in a chain of constructor calls.
In ES5, it is created in the operand of new, the first in a chain of constructor calls.

ES6 involves many more runtime changes, like the new Internal properties featured by functions: [[ConstructorKind]], [[Construct]] and [[HomeObject]], and as the conclusion of the article states, one main point is that now we have different kinds of functions:

  • On one side we have Constructible functions, that I understand are those that can be called with new (as they have [[Construct]]), these are our "normal" functions and the ones created by a constructor definition.
  • Then we have Arrow functions, which have 2 main features: not being constructable, and capturing the lexical this (so for the latter it's as if you had called Function.bind)
  • And then functions created by method definitions, that are not constructable, use dynamic this and can use super.

Classes in ES6 follow a similar approach to other languages regarding missing constructors. If you don't provide a explicit constructor in your class definition, the compiler will add one. This one for a base class: constructor() {}, and this one for a derived class: constructor(...args) {super(...args);} .

As a JavaScript function can receive a different number of parameters, the idea of function overloading that we have in static languages does not exist, and hence a class can have only one constructor function. As a consequence we don't have the minor "problem" that can happen in static languages if a base class misses a parameterless constructor. It's described here

The article mentions something that is missing in ES6 classes and that even deserved an entry on its own, calling a constructor without new. The importance given to something like this really puzzles me. I've never understood the "effort" taken by some libraries (for example underscore.js) in pre ES6 times to allow you to call a fuction that is going to be used to create objects (what in pre-ES6 times we used to call a "constructor function") both with and without new. I'm referring to code like this:

function Person() {
    if (!(this instanceof Person)) return new Person();
    //normal initialization code here 
    //this.property = val;
  };

The idea that this is in case someone forgets to use new seems senseless to me. You have to know the rules... is as if you try to call a method with "->" rather than with "."... Reading the comments Rauschmayer explains that the use case is this :

The use case is if you want to remain flexible w.r.t. implementing something via a class or via a factory function. For my own code, I wouldn’t mind this kind of minor refactoring. For libraries, this becomes more important.

I assume he refers to having a function that receives as parameter another function that will be used to create one object. Your code does not know if that function is a simple one or a "constructor" one, it will just invoke it without new. The designer of that function wants to allow 2 use cases: the one I have just described, where the caller does not really know what function this is, and the normal one where the caller calls this function with new.

function Person() {
    if (!(this instanceof Person)) return new Person();
    //normal initialization code here 
    //this.property = val;
  };

function test(objectCreatorFunc){
   var o1 = objectCreatorFunc();
}

//normal use:
new Person();

//pass it as a factory to the test method
test(Person);

Well, the idea seems useful, but not having it in the language is pretty simple to circunvent, just do the extra step of creating the factory:

class Person() {
    constructor(){
     //this.property = val;
 }
  };

function test(objectCreatorFunc){
   var o1 = objectCreatorFunc();
}

test(function(){
 return new Person();
});

Searching a bit about other people thoughts on this topic I've found articles like this. The guy is really against the use of "new", saying:

The `new` keyword violates both the substitution principle and the open / closed principle. It’s also destructive because it adds zero value to the language, and it couples all callers to the details of object instantiation.

If you start with a class that requires `new` (all classes in ES6) and you later decide you need to use a factory instead of a class, you can’t make the change without refactoring all callers. Take a look at this example gist.

At first thought the idea of discouraging "new" and just using factories can sound odd, but on second thought it makes sense, cause as he says it causes huge coupling. But giving it another thought, we've already heard about avoiding "new" and using Dependency Injection for that. So yes, the idea of being able to call a function (contructor/factory) with or without new is meaningless, what you need is to inject those objects. Indeed, linking this to the feature that I mentioned at the start that in principle I liked (returning a different object from a contructor), it should not be a class itself that decides that it uses a pool of instances of itself, that's something should be managed externally, by the IoC.

Reading that whole post has been a pretty good thought exercise. He mentions that "super" should be deprecated, as it causes heavy coupling. Well, sure, because as he mentions earlier in the article, "extends", that is, inheritance, causes heavy coupling and should be avoided. Well, this is nothing new, it's just the "favor composition over inheritance" principle. In that sense, reading this answer in StackOverflow about why inheritance is heavily coupled has been a more that welcome reminder of why we should do so.

http://js-bits.blogspot.fr/2010/08/constructors-without-using-new.html ------------- https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3#.g3ynqbd4j http://martinfowler.com/bliki/CallSuper.html Dependency Injection

No comments:

Post a Comment