首页 > 解决方案 > Is it possible to know the 'this' object before calling the constructor?

问题描述

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.

标签: javascriptclassecmascript-6constructorthis

解决方案


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 { ... }
);

推荐阅读