6

Before ES6 classes, a function could be used as a constructor:

function MyClass(a, b) {
}

Then, the following code is equivalent to a classic instantiation (like let thisObj = new MyClass("A", "B")):

let thisObj = Object.create(MyClass.prototype)
// Here we know the `this` object before to call the constructor.
// Then, the constructor is called manually:
MyClass.call(thisObj, "A", "B")

… This technique was a way to know the this object before calling the constructor. But Function.prototype.call() doesn't work on an ES6 class constructor.

With ES6 we have Reflect.construct():

let thisObj = Reflect.construct(MyClass, "A", "B");

But it doesn't provide a way to call the constructor after the this object is created.

Is it still possible to do that with ES6 classes?

My use case

I would have needed to keep this feature from ES5 to ES6 for a framework. The framework is responsible for instantiating components (which are ES6 classes). A component can create child components (in a tree of components, there is no inheritance here) from its constructor. Then, a child component can query the framework to get its parent from its own constructor. In this case, we have a technical limitation because the framework still doesn't have the return value of the parent component constructor. This is a regression compared to (a transpilation to) ES5.

Paleo
  • 21,831
  • 4
  • 65
  • 76
  • 2
    Out of interest, what would you use that knowledge for? – deceze Apr 25 '18 at 14:02
  • Just dont define a constructor in the class? – Jonas Wilms Apr 25 '18 at 14:03
  • I'm not even sure why you'd want to, but `Reflect.construct()` is just equivalent to calling `new MyClass(...)`. It will call the ctor because it's constructing the object. If you have additional functionality you'd probably want to put it not in the constructor. – Dave Newton Apr 25 '18 at 14:06
  • 1
    @deceze For a framework. The framework is responsible for instantiating components (which are ES6 classes). A component can create sub-components from its constructor. Then, a sub-component can query the framework to get its parent from its own constructor. In this case, we have a technical limitation because the framework still doesn't have the return value of the parent component constructor. – Paleo Apr 25 '18 at 14:09
  • let's say you have 5 (global) variables: obj1, obj2, obj3, obj4, obj5. Then inside the function you can test if(obj1 === this), for all possible variables. Does that help you? – Emmanuel Delay Apr 25 '18 at 14:24
  • 1
    What specifically are you doing with `thisObj` before calling the constructor? It seems like you're options would be quite limited, since there is tons of stuff that could be done in the constructor, and it hasn't run yet. Maybe you can consider making your framework have a base class that would initialize everything on `this` before the child class runs? At the end of the day, the constructor itself is responsible for deciding what `this` _is_, so if you haven't called the constructor yet, `this` could be literally anything. – loganfsmyth Apr 25 '18 at 15:56
  • 1
    Sounds like a shoddy framework. If component constructors need access to parent components, the parent components should be passed as constructor parameters. – StackOverthrow Apr 25 '18 at 16:05
  • _"shoddy"_?? That's not nice. I do my best to make the framework work as well as possible no matter how it is used. The case I describe here may imply that the user code lacks elegance, so what? If ES6 does not allow to know the `this` object before the call to the constructor, then we have here a regression. – Paleo Apr 25 '18 at 16:21
  • _"What specifically are you doing with thisObj before calling the constructor?"_ Nothing. The framework just stores it. But the user code can query the framework to get it, in order to do what it want. – Paleo Apr 25 '18 at 16:24
  • 1
    No, this is not possible. See [Aare es6 classes just syntactic sugar for the prototypal pattern in javascript?](https://stackoverflow.com/q/36419713/1048572) and more specifically [Is it possible to inherit old-style class from ECMAScript 6 class in JavaScript?](https://stackoverflow.com/q/33369131/1048572) – Bergi Apr 25 '18 at 18:10
  • @Paleo Can you maybe post a new question with the specific ES5 code of the framework, as well as an example of how it is used and what features it requires? Then we can suggest which way to go in ES6. – Bergi Apr 25 '18 at 18:12
  • Thanks @Bergi , I added your two resources to the estus' answer. The second one answers unambiguously. – Paleo Apr 26 '18 at 08:36

1 Answers1

3

It's impossible to do that with ES6 classes. ES6 classes are supposed to be instantiated only with new or Reflect.construct.

Function-calling classes is currently forbidden. That was done to keep options open for the future, to eventually add a way to handle function calls via classes. [source: exploringjs]

See also:

Why this pattern is not viable

Class instance isn't necessary this object that appears in constructor, because ES6 class can return a value from constructor, which is considered class instance:

class Foo {
  constructor {
    // `this` is never used
    return {};
  }
}

A component can create sub-components from its constructor. Then, a sub-component can query the framework to get its parent from its own constructor

This pattern is not viable in ES6 classes. The limitation restricts this from appearing before super.

In order to reach particular classes in hierarchy, decorator pattern can be used to decorate a class when it's defined. This allows to modify its prototype on definition or put an intermediate class between a parent and a child. This approach solves a lot of framework-specific tasks like dependency injection and is used in modern frameworks (Angular, etc.).

A convenient way to do this is ECMAScript Next/TypeScript decorator. Here's an example that shows that a decorator allows to dynamically intercept child constructor and augment child prototype:

let BAZ;

class Foo {
  constructor(foo) {
    console.log(foo);    
  }
}

function bazDecorator(Class) {
  return class extends Class {
    constructor(foo) {
      super(BAZ || foo);
    }

    get bar() {
      return BAZ || super.bar;
    }
  }
}

@bazDecorator
class Bar extends Foo {
  constructor(foo) {
    super(foo);

    console.log(this.bar);
  }

  get bar() {
    return 'bar';
  }
}

// original behaviour
new Bar('foo'); // outputs 'foo', 'bar'

// decorated behaviour
BAZ = 'baz';
new Bar('foo'); outputs 'baz', 'baz'

ES.next decorator is basically a helper function. Even without syntactic sugar, it is still applicable in ES6 with slightly different syntax:

const Bar = bazDecorator(
    class Bar extends Foo { ... }
);
Paleo
  • 21,831
  • 4
  • 65
  • 76
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thanks. _"This pattern is not viable in ES6 classes. The limitation restricts `this` from appearing before `super`."_ Sorry, I was unclear. Sub-components are in fact **child** components in a tree of components. There is no inheritance here. – Paleo Apr 25 '18 at 16:29
  • 1
    I see. Yes, that's different case, yet the answer is the same. This cannot be done in ES6 classes, Object.create recipe isn't relevant anymore. Still, decorators can provide certain benefits at defining a hierarchy of components. The thing you're describing is usually handled with framework lifecycle. E.g. in Angular there is AfterViewInit lifecycle hook - child components aren't available in component constructor but are available in ngAfterViewInit component method, which is called by the framework when child components are ready. That's how it's usually done. Hope this helps. – Estus Flask Apr 25 '18 at 16:41
  • I edited your answer to add a citation on the factual answer. And to separate the other considerations. Then I accepted it. Feel free to update again. – Paleo Apr 25 '18 at 16:50
  • A notice regarding your comment: Lifecycles are mostly relevant for a template engine. – Paleo Apr 25 '18 at 16:55
  • Surely not just for template engine, but yes, there are good reasons why frontend frameworks such as Angular and React have lifecycles. – Estus Flask Apr 25 '18 at 18:54