2

How can I make or simulate a javascript class that extends another class and Array. I have another class Serializable that it also needs to extend to have a serialize and deserialize function.

class ArrayLikeClass extends Serializable({"_title": "raw"}){
  constructor(title){
    this._title = title;
  }
  /* Serializable adds these:
  serialize(){...serializes the class}
  static deserialize(){...deserializes the class}
  // I don't want to add these in manually */
}

Using this I have a class that is serializable. How can I make this have the methods serialize and deserialize from Serializable but also extend Array

I would use mixins as suggested in the possible duplicate, however how can I use mixins to have a static function deserialize

pfg
  • 2,348
  • 1
  • 16
  • 37
  • 2
    `Serializable` should be an interface, not a base class. In JavaScript "interfaces" are implemented through a common practice called "mixins", which should be a good google search term to use in finding a solution. – Patrick Roberts Apr 30 '18 at 18:46
  • @PatrickRoberts How can I use mixins to make a static method – pfg Apr 30 '18 at 18:48
  • Possible duplicate of [Multiple inheritance/prototypes in JavaScript](https://stackoverflow.com/questions/9163341/multiple-inheritance-prototypes-in-javascript) – Patrick Roberts Apr 30 '18 at 18:48
  • @pfg `export default Serializable({_title:"raw"}, class extends Array { … })`. – Bergi Apr 30 '18 at 18:50
  • @pfg `class ArrayLikeClass extends Array { ... }` and `ArrayLikeClass.deserialize = Serializable({_title: "raw"}).deserialize` or something like that. – Patrick Roberts Apr 30 '18 at 18:50

2 Answers2

2

In JavaScript, classes are expressions. You can take advantage of this:

function addSerialize(superclass) {
  return class Serialize extends superclass {
    // serialize class goes here
  }
}

class MyArray extends addSerialize(Array) {
  // MyArray stuff goes here
}

AFAIK, Justin Fagani at Google came up with this approach. He also wrote a little helper library to assist with implementing it.

Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • Is there a way to not provide a superclass using a method like this? I assume extending undefined would throw an error. Edit: going to default superclass to an empty class, this should work – pfg Apr 30 '18 at 18:52
  • I like how this mixin approach mimics multiple inheritance by inserting an extra `prototype` in between with the interface implementation. +1 – Patrick Roberts Apr 30 '18 at 18:52
  • 2
    @pfg of course, just pass it an empty class: `const Serialize = addSerialize(class {});`. Again, classes in ES 6 JavaScript are first-class: you can assign them to vars, pass them as parameters, return them from functions, etc. – Jared Smith Apr 30 '18 at 18:55
  • @pfg the core issue you're experiencing is that multiple inheritance is not supported in JavaScript, since inheritance is `prototype`-based. This is honestly a very elegant way of working around that issue, and while there are other approaches, this seems like the most [DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself) way you're going to get for what you're trying to do. – Patrick Roberts Apr 30 '18 at 18:55
  • @PatrickRoberts yeah it's the kind of thing I wish I'd thought of. I use it all the time when writing webcomponents. – Jared Smith Apr 30 '18 at 18:57
  • 1
    The only downside of this is that you'll need more code, specifically implementing `Symbol.hasInstance` in order to support a semantic `instanceof` operator that pretends there's multiple inheritance going on. – Patrick Roberts Apr 30 '18 at 18:59
  • @PatrickRoberts yeah, that's an unfortunate limitation, but you can always hack around it the same way we do now with mixins. – Jared Smith Apr 30 '18 at 19:00
  • You can make superclass optional really nicely with optional arguments - `function addSerialize(superclass = class{}) {`. Luckily I don't need to support instanceof for this so I don't need to implement that – pfg Apr 30 '18 at 19:05
  • @JaredSmith something like my answer? Or would you have done something simpler? Note the `Object.defineProperty()` is necessary because assigning the function to `Serializable[Symbol.hasInstance]` does not seem to initialize the property with the correct descriptor, which prevents the custom function from being called as part of the `instanceof` check – Patrick Roberts Apr 30 '18 at 20:02
  • @PatrickRoberts `instanceof` should not pretend that there is multiple inheritance. There is no `Serialize` constructor that you could use to check, there is no shared prototype that all "subclasses" inherit from. (Altogether: there is no multiple inheritance :D) – Bergi Apr 30 '18 at 20:43
  • @Bergi if you treat the `addSerialize()` as a class (see my answer where it was refactored to `Serializable()`), you can use the `instanceof` check where you want to know if the `serialize()` and `deserialize()` methods are implemented as expected. I think that's a legitimate use-case where weakly typed inputs need to be validated before treating them as `Serializable` – Patrick Roberts Apr 30 '18 at 20:48
  • @Bergi nevermind, your comment gave me an idea for a better implementation that avoids duck-typing. – Patrick Roberts Apr 30 '18 at 20:54
2

If you want to support instanceof operator, here's an extension to Jared's approach that allows this by defining Symbol.hasInstance memoizing Serializable():

function serialize () {
  // implementation
}

function deserialize (string) {
  // implementation
}

// optional memoization
const Bases = new WeakMap()

// use Object instead of anonymous class {} so that default can be memoized
function Serializable (Base = Object) {
  // optional memoization
  if (Bases.has(Base)) {
    return Bases.get(Base)
  }

  class Serialize extends Base {
    static deserialize () {}
    serialize () {}
  }

  Serialize.deserialize = deserialize
  Serialize.prototype.serialize = serialize

  // optional memoization
  Bases.set(Base, Serialize)

  return Serialize
}

// usage
class ArrayLike extends Serializable(Array) { }

let arrayLike = new ArrayLike()
console.log(arrayLike instanceof Array) // true
console.log(arrayLike instanceof Serializable(Array)) // true
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153