javascript - 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.
解决方案
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:
- Is it possible to inherit old-style class from ECMAScript 6 class in JavaScript?
- are es6 classes just syntactic sugar for the prototypal pattern in javascript?
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 { ... }
);