How to compose a constructor in ES6? - javascript

I tried to write a mixin function based on the ideas described here for inheritance. Method mixins are pretty easy, but I am struggling with constructor mixins.
I try to write a function, which takes a class and returns a new class, with an extended constructor.
This is my example:
function extend_constructor(original, extension) {
let extended = function (...args) {
console.log("extended constructor")
original.prototype.constructor.apply(this, args)
extension.apply(this, args)
}
Object.defineProperty(extended, 'name', Object.getOwnPropertyDescriptor(original, 'name'))
extended.prototype = Object.create(original.prototype)
Object.defineProperty(extended.prototype, 'constructor', {
value: extended,
writeable: true,
configurable: true})
return extended
}
function extension(id) {
console.log(`Extension: ${id}`)
}
I works with function classes.
function Basefunc(id) {
console.log(`Basefunc: ${id}`)
}
new Basefunc(1)
const BasefuncEx = extend_constructor(Basefunc, extension)
new BasefuncEx(2)
But fails with ES6 classes:
class Baseclass {
constructor(id) {
console.log(`Baseclass: ${id}`)
}
}
new Baseclass(3)
const BaseclassEx = extend_constructor(Baseclass, extension)
new BaseclassEx(4)
The error is:
TypeError: Class constructor Baseclass cannot be invoked without 'new'
But I think using new is not what I want, because new will create an instance of the original class and will apply the original constructor to it. I want to apply the original constructor of the original class to the instance of the extended class. And I am wondering how to do this with new.

Related

Difference between proxy in class constructor vs proxy with construct handler

I'm finding different implementations to hook all instances of a class to a proxy.
Inside the class constructor (example):
class Foo {
constructor(...args) {
//...
return new Proxy(this, {
//...
});
}
}
With the construct handler (example):
class Foo {
constructor(...args) {
//...
}
}
new Proxy(Foo, {
construct(Foo, args, newTarget) {
// ...
return Reflect.construct(Foo, [...args], newTarget);
},
//...
});
Is there a difference in these implementations?
I want to make a generic setter and getter; so in the second alternative I would have to create a proxy inside the proxy, which in terms of memory allocation and performance would be the same I think.
Is it so?

Extending express-js in a ES-6 class: trouble binding getter and setter

It is of my interest to work with Express in a extended class. I want to define a getter and setter for a property, but doing so will not bind the methods to the instances. A workaround is to create a custom getter and setter method and binding it on the constructor, but it feels wrong.
const express = require("express");
export default class Application extends express {
_endpoints;
constructor() {
super();
this._endpoints = null;
}
get endpoints() {
return this._endpoints;
}
set endpoints(paths: []) {
this._endpoints = new Set(...paths);
}
}
const myApp = new Application();
console.log(myApp) // ...express-app-object, _endpoints, and nothing related to the getter and setter defined.
console.log(myApp._endpoints) // null
console.log(myApp.endpoints) // undefined
It apparently has to do with the fact that express is not a class or a constructor, it's a factory function. When you call super(), it calls express() and that creates a whole new object, that is not your object and does not have your prototype. Referencing myApp._endpoints works because your constructor assigned that property in your constructor to the object that express() created, but that object does not have your prototype attached to it, it's a new object that Express created.
If you remove the extends express from your code and comment out the super(), you will see that it all works.
If you change express to any other actual defined class in Javascript, your endpoints() getter works just fine.
So, you can't extend express in this manner.
The express prototype is available and you can add things to it the way we used to before we had the class keyword.
I would not recommend using a class here at all because it implies to anyone reading your code that they can define methods for the class and expect them to work (which they do not).
Instead, just define your own factory function that calls the express factory function and then adds properties to the object it returns:
// factory function for my app
function Application() {
const app = express();
// add instance properties
app._endpoints = null;
// add getters/setters
Object.defineProperty(app, "endpoints", {
get: function () {
return this._endpoints;
},
set: function (paths: any[]) {
this._endpoints = new Set([...paths]);
},
enumerable: true,
configurable: true,
});
return app;
}
This can be used either as:
app = Application();
or:
app = new Application();
export class Application extends express {
private _endpoints = null;
constructor() {
super();
Object.defineProperty(this, "endpoints", {
get: function () {
return this._endpoints;
},
set: function (paths: any[]) {
this._endpoints = new Set([...paths]);
},
enumerable: true,
configurable: true,
});
}
}
Usage would be normal:
const app = new Application();
console.log(app.endpoints) // null
app.endpoints = ['/auth/signin', '/auth/signout']
console.log(app.endpoints) // Set(2) { '/auth/signin', '/auth/signout' }

How to read instance decorators in typescript?

I can create custom decorator using reflect-metadata and it work fine.
Problem is, that I don`t know how to get all instance decorators.
import 'reflect-metadata';
console.clear();
function readTypes() {
const decorator: MethodDecorator = (target, propertyKey, description) => {
const args = Reflect.getMetadata(
'design:paramtypes',
target,
propertyKey
).map(c => c.name);
const ret = Reflect.getMetadata('design:returntype', target, propertyKey);
console.log(`Arguments type: ${args.join(', ')}.`);
console.log(`Return type: ${ret.name}.`);
};
return decorator;
}
class Foo {}
class Bar {
#readTypes()
public fn(a: number, b: string, c: Foo): boolean {
return true;
}
}
const barInstance = new Bar();
I would like to get all functions with decorator #readTypes from barInstance. How can I do it?
See working example:
https://stackblitz.com/edit/decorators-metadata-example-nakg4c
First of all, you aren't writing any metadata, only reading it. If you want to lookup which properties were decorated, then you have to write metadata to those properties.
For simplicity's sake, let's simplify the decorator to:
// It's a best practice to use symbol as metadata keys.
const isReadTypesProp = Symbol('isReadTypesProp')
function readTypes() {
const decorator: MethodDecorator = (target, propertyKey, description) => {
Reflect.defineMetadata(isReadTypesProp, true, target, propertyKey);
};
return decorator;
}
Now when the decorator is used, it's executed when the class is parsed, not when instances are created. This means that target in the decorator function is actually the prototype of the class constructor function.
In other words target is the same object as
Bar.prototype or barInstance.constructor.prototype or barInstance.__proto__.
Knowing that we can loop over all the property names in the prototype object and look up that metadata we set earlier:
function getReadTypesPropsFromInstance(object: {
constructor: {
prototype: unknown;
};
}) {
const target = object.constructor.prototype;
const keys = Object.getOwnPropertyNames(target);
return keys.filter(key => Reflect.getMetadata(isReadTypesProp, target, key));
}
Which now returns the names of the decorated properties:
const barInstance = new Bar();
console.log(getReadTypesPropsFromInstance(barInstance)); // ["fn"]
Working example

Default Object Property

I am playing with ES6 classes and to better manage an array property of the class, I replaced the array with an object and added all the array-related functions (get, add, remove, etc) along with an array sub-property:
class MyClass {
constructor () {
this.date_created = new Date()
}
posts = {
items: [],
get: () => {
return this.posts.items
},
add: (value) => this.posts.items.unshift(value),
remove: (index) => this.posts.items.splice(index, 1)
}
}
So it got me thinking: is there a way to setup that posts object to return the items array by default? i.e. through: MyClass.posts
I thought I could trick it with the get() but didn't work.
If you want to keep the actual array hidden and untouchable except through the methods, it has to be declared in the constructor. Any function that alters it has to be declared in the constructor as well. If that item is to be publicly accessible, it has to be attached to this.
class Post extends Array
{
add(val)
{
this.unshift(val);
}
remove()
{
this.shift();
}
}
class MyClass
{
constructor()
{
this.date_created = new Date()
this.post = new Post();
}
}
let x = new MyClass();
console.log(x.post);
x.post.add(2);
console.log(x.post);
class MyClass {
constructor() {
this.post = [];
}
get post() {
return [1,2,3]
}
set post(val) {
}
}
let someClass = new MyClass();
console.log(someClass.post)
I believe the correct syntax is something as above. your getter and setter is pointing to post, where post is only a variable/key of MyClass, hence your getter and setter should be at MyClass level

Why method added through decoration is not accessible

Suppose that I have a Directive decorator which adds a static method to it's target called factory:
function Directive<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
static factory(...args): ng.IDirectiveFactory {
const c: any = () => {
return constructor.apply(this, args);
};
c.prototype = constructor.prototype;
return new c(...args);
}
};
}
I also add the type via an interface:
interface BaseDirective extends ng.IDirective {
factory(): ng.IDirectiveFactory;
}
Why in my class declaration of:
#Directive
class FocusedDirective implements BaseDirective {....
I get a Class 'FocusedDirective' incorrectly implements interface 'BaseDirective'.
Property 'factory' is missing in type 'FocusedDirective'.
Am I wrong to expect from #Directive to add this missing property for me?
Decorators can't change the type of the class, you can invoke your decorator as a function and store the new class which will contain the method and use the new class instead of the original:
const FocusedDirectiveWithDirective = Directive(FocusedDirective);
You can do away with the intermediate class altogether by using class expressions:
const FocusedDirective = Directive(class implements BaseDirective{
});
You have two problems. The first has little to do with decorators: factory is a static method in your implementation, but a regular method in your interface:
interface BaseDirective {
factory(): ng.IDirectiveFactory;
}
That's going to be a problem for you. For now I'm going to convert the implementation to a regular method, since it's simpler to implement.
function Directive<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
factory(...args: any[]): ng.IDirectiveFactory {
const c: any = () => {
return constructor.apply(this, args);
};
c.prototype = constructor.prototype;
return new c(...args);
}
};
}
The second issue: decorators do not mutate the class signature the way you're expecting. This is an oft-requested feature and there are some interesting issues around why it's not a simple problem to solve. Importantly, it's not easy to figure out how to support having the implementation of the class refer to the mutated type. In your case: would the stuff inside your {.... know about factory() or not? Most people seem to expect it would, but the decorator hasn't been applied yet.
The workaround is not to use decorator syntax at all, but instead to use the decorator function as a regular function to create a new class. The syntax looks like this:
class FocusedDirective extends Directive(class {
// any impl that doesn't rely on factory
prop: string = "hey";
foo() {
this.prop; // okay
// this.factory(); // not okay
}
}) implements BaseDirective {
// any impl that relies on factory
bar() {
this.prop; // okay
this.foo(); // okay
this.factory(); // okay
}
}
This also solves the "does the implementation know about the decorator" issue, since the stuff inside the decorator function does not, and the stuff outside does, as you see above.
Back to the static/instance issue. If you want to enforce a constraint on the static side of a class, you can't do it by having the class implement anything. Instead, you need to enforce the static side on the class constructor itself. Like this:
interface BaseDirective {
// any actual instance stuff here
}
interface BaseDirectiveConstructor {
new(...args: any[]): BaseDirective;
factory(): ng.IDirectiveFactory;
}
class FocusedDirective extends Directive(class {
// impl without BaseDirectiveConstructor
}) implements BaseDirective {
// impl with BaseDirectiveConstructor
bar() {
FocusedDirective.factory(); // okay
}
}
function ensureBaseDirectiveConstructor<T extends BaseDirectiveConstructor>(t: T): void {}
ensureBaseDirectiveConstructor(FocusedDirective);
The ensureBaseDirectiveConstructor() function makes sure that the FocusedDirective class constructor has a static factory() method of the right type. That's where you'd see an error if you failed to implement the static side.
Okay, hope that helps. Good luck.

Categories

Resources