2

How can we dynamically/programmatically extend a javascript class?

More concretely, given something like

class Polygon {
  constructor(area, sides) {
    this.area = area;
    this.sides = sides;
  }
}

const Rectangle = extend(Polygon, (length, width) => {
  super(length * width, 4);
  this.length = length;
  this.width = width;
});

how can we implement something like extend such that it behaves the same as

class Rectangle extends Polygon {
  constructor(length, width) {
    super(length * width, 4);
    this.length = length;
    this.width = width;
  }
}

?

1

5 Answers 5

3

This worked for me:

// Normal class to be extended
class A {
  constructor(msg, name = 'A') {
    console.log('A: ' + msg + ' from ' + name)
    document.getElementById("demo").innerHTML += 'A: ' + msg + ' from ' + name + '<br>'
  }
}

// New class B which extends A
const B = (msg, name) => class Child extends A {
  constructor(/*func params is used instead*/) {
    super(msg, name || 'B')
  }
}

// New class C which extends B which extends A
const C = (msg, name) => class Child extends B(msg, name) {
  constructor(/*func params is used instead*/) {
    super(msg)
  }
}

// New class D which extends C which extends B which extends A
const D = (...args) => class Child extends C(args) {
  constructor(/*func params is used instead*/) {
    super(args)
  }
}

new A('Hello') // A: Hello from A
new (B('Hi')) // A: Hi from B
new (C('Hey from C and')) // A: Hey from C and from B
new (D('Hey from D and C and')) // A: Hey from D and C and from B

This will dynamically extend any given parent class

https://jsfiddle.net/u15482b6/2/

Sign up to request clarification or add additional context in comments.

Comments

1

There are three problems here:

(1) super is only available inside object methods, so there is no way to access super in an arrow function. That needs to be somehow replaced by a regular function call.

(2) Classes can only be constructed, not called (unlike functions acting as constructors). Therefore you cannot just .call the classes constructor onto the "subclass" instance. You have to create an instance of the superclass and copy that into the subclass, eventually loosing getters / setters.

(3) Arrow functions have a lexical this, so you cannot access the instance with this inside an arrow function.

Given these three problems, a viable alternative would be:

  function extend(superclass, constructor) {
    function Extended(...args) {
      const _super = (...args) => Object.assign(this, new superclass(...args));
      constructor.call(this, _super, ...args);
    }
    Object.setPrototypeOf(Extended, superclass);
    Object.setPrototypeOf(Extended.prototype, superclass.prototype);
    return Extended;
 }

  const Rectangle = extend(Polygon, function(_super, length, width) {
     _super(/*...*/);
     /*...*/
  });

But honestly ... what's wrong with the native class ... extends ?

3 Comments

Thanks for the informative straightforward answer. class ... extends is obviously the way to go most of the time, but I'm looking for a solution to be able to programmatically extend classes based on some input data. Specifically I'm trying to create web components by extending HTMLElement, however your solution doesn't seem to work for that case (new HTMLElement() gives "TypeError: Illegal constructor."). Any suggestions to get that working?
Cause HTMLElement is not a class, it's an interface. Use a class that implements it.
Like this jsfiddle.net/6kbnrwuL/1 ? I'm still seeing the same issue.
1

After some hacking around, I've found that this horrifyingly works.

function extend(superclass, construct) {
    return class extends superclass {
        constructor(...args) {
            let _super = (...args2) => {
                super(...args2)
                return this;
            };
            construct(_super, ...args);
        }
    };
}

const Rectangle = extend(Polygon, function(_super, length, width) {
         let _this = _super(length * width, 4);
         _this.length = length;
         _this.width = width;
    });

2 Comments

Weirdly, yes it does. Although I have only tested firefox. This is what I'm working on where it's ok so far github.com/kag0/componentry/blob/master/componentry.js
@kag0, I guess the only way to avoid having to pass around _super and using _this would be to create the new class using eval :)
0

class A {
  m () { 
    console.log('A')
  }
}

class B extends A {
  m () { 
    console.log('B')
  }
}

var a = new A()
var b = new B()
a.m()
b.m()

const MixinClass = superclass =>
  class extends superclass {
    m () {
      console.log('extended')
    }
 }

const extendsAnyClass = AnyClass => 
    class MyMixinClass extends MixinClass(AnyClass) {}

var AA = extendsAnyClass(A)
var BB = extendsAnyClass(B)
var aa = new AA()
var bb = new BB()
aa.m()
bb.m()

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
0

I needed to extend a google maps js lib, which is loaded async, and only notified ready by a callback. So

<script src="google?callback=xxx">
<script src="me">

wont work as google.maps may not exist when my code defines class extends google.maps.xxx.

So I used an init function

init = () => { class child extends google.xxx { ... } }

If you don't know the name of your extended class until runtime, you can use eval If you do, be careful about the security implications

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.