Is it possible to use es6 constructor instructions on another instance by changing the "this" context (call, apply or other)? This is possible using es5 "classes". Here is a small example of what I mean:
function ES5() {
this.foo = 'foo';
}
class ES6 {
constructor() {
this.bar = 'bar';
}
}
var a = new ES6();
ES5.call(a);
console.log(a.foo + a.bar); //foobar
var b = new ES5();
//Reflect.construct(ES6); ??
ES6.call(b); //TypeError: Class constructor ES6 cannot be invoked without 'new'
console.log(b.foo + b.bar); //how to get foobar here too?
Edit: My question has nothing to do with the new keyword. The answer I'm looking for is how to run the instructions placed in an es6 constructor using another "this" context (with or without the new keyword).
New Features in ES6 1 The let keyword 2 The const keyword 3 JavaScript Arrow Functions 4 JavaScript For/of 5 JavaScript Map Objects 6 JavaScript Set Objects 7 JavaScript Classes 8 JavaScript Promises 9 JavaScript Symbol 10 Default Parameters More items...
ES6 - Syntax. Comments − Used to improve code readability. These are ignored by the JavaScript engine. Identifiers − These are the names given to elements in a program like variables, functions, etc. The rules for identifiers are − Identifiers can include both, characters and digits. However, the identifier cannot begin with a digit.
ES6 added the following methods to the Math object: 1 Math.trunc () 2 Math.sign () 3 Math.cbrt () 4 Math.log2 () 5 Math.log10 ()
ES6 allows function parameters to have default values. The rest parameter (...) allows a function to treat an indefinite number of arguments as an array: The includes () method returns true if a string contains a specified value, otherwise false: let text = "Hello world, welcome to the universe.";
As the comments and yourself have pointed out, trying to invoke class constructors with a custom this
context is really not something you want to attempt if there is any way around it. This was made hard intentionally!
If for some reasons this is unavoidable enough to justify tricky workarounds, you can find two partial solutions below. They are both imperfect in their own ways - depending on your exact situation one of them may still fit your needs.
While it is impossible to set this
directly in a constructor call, it is possible to set the prototype of this
to an object of your choice.
To do so you can use Reflect.construct()
to call the internal [[Construct]]
method with a custom new.target
value. this
will then get initialised to an object inheriting from new.target.prototype
.
Building on your example:
function ES5() {
this.foo = 'foo';
}
class ES6 {
constructor() {
this.bar = 'bar';
}
}
let b = new ES5();
function TemporaryHelperConstructor() {}
TemporaryHelperConstructor.prototype = b;
b = Reflect.construct( ES6, [], TemporaryHelperConstructor ); // The third argument corresponds to the value of new.target
console.log( b.foo + b.bar ); // foobar !
(The exact workings of Reflect.construct()
and the internal [[Construct]]
method are described in sections 26.1.2 and 9.2.2 of the specs)
this
bound to b
, it is called with this
bound to an empty object directly inheriting from b
. This may lead to problems if you or the class constructor rely on methods like Object.getOwnPropertyNames()
, Object.getPrototypeOf()
etc.While it is impossible to invoke the internal [[Call]]
method of a class constructor without causing a TypeError
, it is possible to extract the code block attached to the class constructor and create an ordinary function out of it, which you may then call with a custom this
binding.
You can use the Function.prototype.toString()
method to extract the code block of the class constructor as a string. The Function()
constructor can then make an ordinary function out of this string, which you may call with a custom this
binding through Function.prototype.apply()
.
Building on your example:
function ES5() {
this.foo = 'foo';
}
class ES6 {
constructor() {
this.bar = 'bar';
}
}
const b = new ES5();
const constructorBody = ES6.toString().match( /(?<=constructor\(\) ){[^}]*}/ )[0]
const ordinaryFunction = Function( constructorBody )
ordinaryFunction.apply( b ); // No TypeError
console.log( b.foo + b.bar ); // foobar !
Note that this snippet uses an extremely simplified regular expression for demonstration purposes. To make things robust you would need to take into account nested curly braces and curly braces in strings and comments. You would also need to extract the constructor arguments if they are needed.
(According to section 19.2.3.5 of the specs, you can rely on a consistent enough output of Function.prototype.toString()
for this approach to work across implementations.)
new.target
will be set to undefined
when executing the ordinary function (as is always the case with [[Call]]
invocations), which may cause problems if the class constructor was using it.Function()
(MDN), which may cause ReferenceErrors
if the class constructor was relying on them.SyntaxError
if applied on a derived class using super()
, which is not valid syntax in ordinary functions.There is no perfect solution to your problem. If your use case is simple enough, you may however still be able to achieve what you want. The partial workarounds will come with pernicious issues of their own - tread with care!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With