I couldn't find the solution to this issue. I am banging my head against a wall entire day.
Assume we have the following hierarchy of classes:
class A {
async name() { ... }
}
class B extends A {
async age() { ... }
}
class C extends A {
async group() { ... }
}
class D extends B {
constructor() {
super(...)
this.C = new C(...);
}
async group() { return await this.C.group(); }
}
Because JavaScript doesn't support multiple inheritance, I have an issue. I would like to combine prototypes of objects in a way that I can invoke function group() without the need for writing repetitive code like above. It is absolute must to be able to invoke group() directly on the object D outside of the class, even though it is the an instance of class C.
How can I combine their prototypes? I've tried things like
class A {
appendPrototype(obj) {
if (obj instanceof A)
for (let k in obj.__proto__) {
this.__proto__[k] = obj.__proto__[k];
}
}
}
.
.
. // Same two classes, B and C
class D extends B {
constructor(...) {
super(...);
const c = new C(...);
this.appendPrototype(c);
}
}
I can't invoke the function group() an instance of the class D. Why is that? How can I achieve it, without writing repetitive code like in the first example?
What you can do, it is to assign to the prototype of the class D, the method of the class C.
In this way all the instances of D will have the group method which is the same method defined within the class C, and you can do the same for all the other child classes.
class A {
async name() {
console.log('A - name');
}
}
class B extends A {
async age() {
console.log('B - age');
}
}
class C extends A {
async group() {
console.log('C - group');
}
}
class D extends B {
constructor() {
super();
}
}
D.prototype.group = C.prototype.group;
const d = new D();
d.group();
Anyway, in your case, I would go for composition (like you did with C within D) and then call the methods like:
d.c.group()
which allows you to avoid the appendPrototype logic.
Related
I would like to be able to instantiate a new instance of a subclass from inside a superclass method.
If I have just a single class with no inheritance, it is straight forward:
class A {
static build(opts) {
return new A(opts)
}
makeAnother() {
return A.build(...)
}
}
const a = new A()
const a2 = a.makeAnother()
This works. However, it doesn't work with subclassing:
class B extends A { ... }
const b = new B()
const b2 = b.makeAnother() // this returns an instance of A, not B
I suppose I could add the build & makeAnother methods to each subclass, but I would rather not repeat things.
You can reference this.constructor inside the super class to get to the constructor of the subclass (or the super class itself, if the method is called on a super instance rather than a sub instance):
class A {
static build(theClass) {
return new theClass()
}
makeAnother() {
return A.build(this.constructor)
}
}
const a = new A()
const a2 = a.makeAnother()
console.log(a2 instanceof A);
class B extends A { }
const b = new B()
const b2 = b.makeAnother()
console.log(b2 instanceof B);
You'll want to use
class A {
static build(opts) {
return new this(opts)
}
makeAnother() {
return this.constructor.build(...)
}
}
Unless build does more than shown here, you don't need it at all of course, you'd rather directly return new this.constructor(...) in makeAnother.
I'm trying to add extra methods to class, and these extra methods should use the super methods.
If I add them in the model definition, it works.
class A {
doSomething() {
console.log('logSomething');
}
}
class B extends A {
doSomething() {
super.doSomething();
console.log('logSomethingElse');
}
}
If I try to add the extra method to B.prototype, I'll get SyntaxError: 'super' keyword unexpected here.
class A {
doSomething() {
console.log('logSomething');
}
}
class B extends A {
}
B.prototype.doSomething = function doSomething() {
super.doSomething();
console.log('logSomethingElse');
}
It is quite clear, why I get this error. This is a function and not a class method.
Let's try to define the method as a class method, and copy it to the original B class:
class A {
doSomething() {
console.log('logSomething');
}
}
class B extends A {}
class X {
doSomething() {
super.doSomething();
console.log('2 logSomethingElse');
}
}
B.prototype.doSomething = X.prototype.doSomething;
In this case I'll get TypeError: (intermediate value).doSomething is not a function.
Is there any way to define methods (that refer to super) outside from the original class definition, and add these methods later to the original class?
super refers to ancestor of a class where the method was defined, it isn't dynamic. As Babel output illustrates this, super is hard-coded to Object.getPrototypeOf(X.prototype), and thus orphan class like this one doesn't make sense because it doesn't have super:
class X {
doSomething() {
super.doSomething();
...
}
}
But super can be substituted with dynamic counterpart:
doSomething() {
const dynamicSuper = Object.getPrototypeOf(this.constructor.prototype);
// or
// const dynamicSuper = Object.getPrototypeOf(Object.getPrototypeOf(this));
dynamicSuper.doSomething();
...
}
class B extends A {}
B.prototype.doSomething = doSomething;
In this case it will refer to ancestor class of class instance where doSomething was assigned as prototype method.
While I think this could be assumed as anti-pattern, you shouldn't use super outside from a class.
You can achieve that using Object Literals.
Refer to Object.setPrototypeOf
const A = {
sayHello() {
console.log("I am A");
},
Factory() {
return Object.create(this);
}
}
const B = {
sayHello() {
super.sayHello();
}
}
Object.setPrototypeOf(B, A);
const c = B.Factory();
c.sayHello();
If you don't make class X inherit from class B or A, the only way to call the method is A.prototype.doSomething() or more generally A.prototype.doSomething.call(this_substitute, ...args).
I have a class map as shown below:
class A {
do(input) { return input + ' A'; }
}
class B extends A {
do(input) { return input + ' B'; }
run() { return `This call: ${this.do()}, super call: ${super.do()}`; }
}
class C extends B {
do(input) { return input + ' C'; }
}
let inst = new C();
console.log(inst.run());
I expect one of two situations:
In this call this.do will be class C do and super.do will be class B do (as we call run from C and his super is B)
In this call this.do will be class B do and super.do will be class A do (as we declare run in B and his super is A)
But i find out that this and super have different contexts: this is class C and super is class A.
My real task is running only class B do and i can't find the way to do it.
Now that JavaScript has classes I'm wondering how it is possible to invoke a super constructor outside of a class constructor.
My unsuccessful naive attempt (results in a SyntaxError):
class A
{
constructor() { this.a = 1; }
}
function initB()
{
super(); // How to invoke new A() on this here?
this.b = 2;
}
class B extends A
{
constructor() { initB.call(this); }
}
I'm aware that in some other language like Java a super constructor can only be invoked inside the constructor of a derived class, but ES6 classes are syntactic sugar for prototype-based inheritance, so I'd be surprised if this were not feasible using built-in language features. I just can't seem to figure out the proper syntax.
The best I've come with so far feels terribly like cheating:
class A
{
constructor() { this.a = 1; }
}
function initB()
{
let newThis = new A();
newThis.b = 2;
return newThis;
}
class B extends A
{
constructor() { return initB(); }
}
Every constructor of a class that extends something must contain a direct super(…) call.
Direct super(…) calls can only be placed in constructors. There's really no way around this.
You really should not place the initialisation logic of a class anywhere else than in its constructor. The straightforward and proper solution is not to use initB at all:
class A {
constructor() { this.a = 1; }
}
class B extends A {
constructor() {
super();
this.b = 2;
}
}
That said, there is a way to subvert the "super() call must be in the constructor" requirement. Putting it inside an arrow function counts as well! So you could do
class A {
constructor() { this.a = 1; }
}
function initB(_super) {
var b = _super();
b.b = 2;
}
class B extends A {
constructor() {
initB(() => super());
}
}
Promise me to not ever do that, please.
Another pattern is not to call super() at all, which works as long as you return an object from the constructor. With that, you can put the actual construction of the object anywhere else:
class A {
constructor() { this.a = 1; }
}
function makeB() {
var b = Reflect.construct(A, [], B); // call the A constructor with B for the prototype
b.b = 2;
return b;
}
class B extends A {
constructor() {
return makeB();
}
}
Which really isn't much better.
It seems possible to nest a class in a constructor which can then be instantiated from anywhere within the class, is this official?
[EDIT] E.g.,
class C {
constructor() {
class D {
constructor() { }
}
}
method() {
var a = new D(); // works fine
}
}
//var a = new D(); // fails in outer scope
The traceur generated JS https://google.github.io/traceur-compiler/demo/repl.html
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";
var C = function C() {
var D = function D() {};
($traceurRuntime.createClass)(D, {}, {});
};
($traceurRuntime.createClass)(C, {method: function() {
var a = new D();
}}, {});
return {};
});
//# sourceURL=traceured.js
No, there are no nested class scopes in ES6, and there is no such thing as private members in the class syntax anyway if you mean that.
Of course you can put a second class as a static property on another class, like this:
class A {
…
}
A.B = class {
…
};
or you use an extra scope:
var C;
{
class D {
constructor() { }
}
C = class C {
constructor() { }
method() {
var a = new D(); // works fine
}
}
}
(There seems to be a bug with traceur as it uses a hoisted var for the class declaration instead of block scope)
With the class field syntax, it will also be possible to write a single expression or declaration:
class A {
…
static B = class {
…
}
};
something like that?
class A {
constructor () {
this.B = class {
echo () {
console.log('I am B class');
}
}
}
echo () {
this.b = new this.B;
this.b.echo();
}
}
var a = new A;
a.echo();
You could use a getter:
class Huffman {
constructor() { /* ... */ }
static get Node() {
return class Node {
constructor() {
var API = this;
API.symbol = 0; API.weight = 0;
return API;
}
};
}
get Node() {
return Huffman.Node;
}
encode() { /* ... */ }
decode() { /* ... */ }
/* ... */
}
// usage
huffman = new Huffman;
new huffman.Node;
new Huffman.Node;
Which in latest Chrome Dev 44.0.2376.0 on Apple 10.10.2 gives in console
new huffman.Node
Node {symbol: 0, weight: 0}
new Huffman.Node
Node {symbol: 0, weight: 0}
In other news, getters are the secret sauce that let's you do a whole bunch of cool things in ES6.
Please Note The above construction breaks instanceof for Node (why? because a whole new class is defined with every get call). To not break instanceof define Node outside of the scope of a single getter, either in the constructor (disabling the Huffman.Node class property and causing instanceof to work within the namespace of a single Huffman instance, and break outside that), or define Node in a sibling or ancestor scope to Huffman (allowing instanceof to work in all scopes below that the one where Node is defined).
When you create a nested child class in a constructor of a parent class, this means every instance of the parent class has its own child class. Typically this is not what you want. Instead you want a child class, which is shared among all instances of the parent class. This means the nested class must be static. This is an example:
class Parent
{
static Child = class Child {
constructor (name) { console.log (`Child: ${name}`); }
}
constructor (...names) {
console.log ('Parent');
this.children = names.map (name => new Parent.Child (name));
}
}
var p = new Parent ('Alice', 'Bob');
console.log (`same type? ${p.children[0].constructor === p.children[1].constructor}`);