create a subclass of the given cls class through composition rather than ES6 inheritance (aka extends).
the composed instance resides under the property this._super, and all instance methods of the super class are copied/mirrored in the output class.
TODO: provide a motivation/justification when compositional-mirroring subclass might be necessary to use instead of ES6 extends inheritance.
// `B` mirrors the constructor of `A`, as well as provide an additional `"_super"` property to access the enclosed (class which is `A`) Bsatisfies { new(amount: number): B<any> } B._supersatisfiestypeofA // the following cannot be satisfied due to `A`'s restrictive constructor: `B satisfies { new(item1: string, item2: string, item3: string): B<string> }` // even though `A`'s super class (`Array`) does permit that kind of constructor signature: Arraysatisfies { new(item1: string, item2: string, item3: string): Array<string> }
constmy_array = newB<number>(5)
// `my_array` encloses an instance of its "subclass" (which is an instance of class `A`) under the `"_super"` key. assertEquals(my_arrayinstanceofB, true) assertEquals(my_array._superinstanceofA, true) my_array._supersatisfiesA<unknown> // this should have been narrowed to `satisfies A<number>`, but typing class extensions with generics is nearly impossible
// `my_array` mirrors the properties and methods of the instances of the "subclass" that it encloses. my_array.fill(1) my_array.push(2) my_array.push(3) assertEquals(my_array.at(-1), 3) assertEquals(my_array._super.at(-1), 3) assertEquals(my_array.length, 7) assertEquals(my_array._super.length, 7)
// `my_array` mirrors the symbolic properties and methods as well. assertEquals([...my_array], [1, 1, 1, 1, 1, 2, 3]) // the `Symbol.iterator` method is mirrored successfully
// `my_array` when a method that returns `new this.constructor(...)` (such as `Array.prototype.splice`), then an instance of // the enclosed subclass `A` will be created instead of `B` (had we had a regular inheritance via `class B extends A { }`). const splice_of_my_array = my_array.splice(0, 4, 0, 0), slice_of_my_array = my_array.slice(3, 4) assertEquals(splice_of_my_arrayinstanceofA, true) assertEquals(slice_of_my_arrayinstanceofA, true) assertEquals([...splice_of_my_array], [1, 1, 1, 1]) assertEquals([...slice_of_my_array], [2]) assertEquals([...my_array], [0, 0, 1, 2, 3]) assertEquals([...my_array._super], [0, 0, 1, 2, 3])
// the class `B` also mirrors static properties and static methods of `A` (and its inherited ones). // this means that any static method that creates a new instance via `new this(...)` will create an instance of `A` instead of `B`. const new_array_1 = B.from(["hello", "world"]), new_array_2 = B.of("goodbye", "world") assertEquals(new_array_1instanceofA, true) assertEquals(new_array_2instanceofA, true) assertEquals(new_array_1, A.from(["hello", "world"])) assertEquals(new_array_2, A.of("goodbye", "world"))
create a subclass of the given
cls
class through composition rather than ES6 inheritance (akaextends
). the composed instance resides under the propertythis._super
, and all instance methods of the super class are copied/mirrored in the output class.TODO: provide a motivation/justification when compositional-mirroring subclass might be necessary to use instead of ES6
extends
inheritance.Example