How to extend Function with ES6 classes? How to extend Function with ES6 classes? javascript javascript

How to extend Function with ES6 classes?


The super call will invoke the Function constructor, which expects a code string. If you want to access your instance data, you could just hardcode it:

class Smth extends Function {  constructor(x) {    super("return "+JSON.stringify(x)+";");  }}

but that's not really satisfying. We want to use a closure.

Having the returned function be a closure that can access your instance variables is possible, but not easy. The good thing is that you don't have to call super if you don't want to - you still can return arbitrary objects from your ES6 class constructors. In this case, we'd do

class Smth extends Function {  constructor(x) {    // refer to `smth` instead of `this`    function smth() { return x; };    Object.setPrototypeOf(smth, Smth.prototype);    return smth;  }}

But we can do even better, and abstract this thing out of Smth:

class ExtensibleFunction extends Function {  constructor(f) {    return Object.setPrototypeOf(f, new.target.prototype);  }}class Smth extends ExtensibleFunction {  constructor(x) {    super(function() { return x; }); // closure    // console.log(this); // function() { return x; }    // console.log(this.prototype); // {constructor: …}  }}class Anth extends ExtensibleFunction {  constructor(x) {    super(() => { return this.x; }); // arrow function, no prototype object created    this.x = x;  }}class Evth extends ExtensibleFunction {  constructor(x) {    super(function f() { return f.x; }); // named function    this.x = x;  }}

Admittedly, this creates an additional level of indirection in the inheritance chain, but that's not necessarily a bad thing (you can extend it instead of the native Function). If you want to avoid it, use

function ExtensibleFunction(f) {  return Object.setPrototypeOf(f, new.target.prototype);}ExtensibleFunction.prototype = Function.prototype;

but notice that Smth will not dynamically inherit static Function properties.


This is an approach to creating callable objects that correctly reference their object members, and maintain correct inheritance, without messing with prototypes.

Simply:

class ExFunc extends Function {  constructor() {    super('...args', 'return this.__self__.__call__(...args)')    var self = this.bind(this)    this.__self__ = self    return self  }  // Example `__call__` method.  __call__(a, b, c) {    return [a, b, c];  }}

Extend this class and add a __call__ method, more below...

An explanation in code and comments:

// This is an approach to creating callable objects// that correctly reference their own object and object members,// without messing with prototypes.// A Class that extends Function so we can create// objects that also behave like functions, i.e. callable objects.class ExFunc extends Function {  constructor() {    super('...args', 'return this.__self__.__call__(...args)');    // Here we create a function dynamically using `super`, which calls    // the `Function` constructor which we are inheriting from. Our aim is to create    // a `Function` object that, when called, will pass the call along to an internal    // method `__call__`, to appear as though the object is callable. Our problem is    // that the code inside our function can't find the `__call__` method, because it    // has no reference to itself, the `this` object we just created.    // The `this` reference inside a function is called its context. We need to give    // our new `Function` object a `this` context of itself, so that it can access    // the `__call__` method and any other properties/methods attached to it.    // We can do this with `bind`:    var self = this.bind(this);    // We've wrapped our function object `this` in a bound function object, that    // provides a fixed context to the function, in this case itself.    this.__self__ = self;    // Now we have a new wrinkle, our function has a context of our `this` object but    // we are going to return the bound function from our constructor instead of the    // original `this`, so that it is callable. But the bound function is a wrapper    // around our original `this`, so anything we add to it won't be seen by the    // code running inside our function. An easy fix is to add a reference to the    // new `this` stored in `self` to the old `this` as `__self__`. Now our functions    // context can find the bound version of itself by following `this.__self__`.    self.person = 'Hank'    return self;  }    // An example property to demonstrate member access.  get venture() {    return this.person;  }    // Override this method in subclasses of ExFunc to take whatever arguments  // you want and perform whatever logic you like. It will be called whenever  // you use the obj as a function.  __call__(a, b, c) {    return [this.venture, a, b, c];  }}// A subclass of ExFunc with an overridden __call__ method.class DaFunc extends ExFunc {  constructor() {    super()    this.a = 'a1'    this.b = 'b2'    this.person = 'Dean'  }  ab() {    return this.a + this.b  }    __call__(ans) {    return [this.ab(), this.venture, ans];  }}// Create objects from ExFunc and its subclass.var callable1 = new ExFunc();var callable2 = new DaFunc();// Inheritance is correctly maintained.console.log('\nInheritance maintained:');console.log(callable2 instanceof Function);  // trueconsole.log(callable2 instanceof ExFunc);  // trueconsole.log(callable2 instanceof DaFunc);  // true// Test ExFunc and its subclass objects by calling them like functions.console.log('\nCallable objects:');console.log( callable1(1, 2, 3) );  // [ 'Hank', 1, 2, 3 ]console.log( callable2(42) );  // [ 'a1b2', Dean', 42 ]// Test property and method accessconsole.log(callable2.a, callable2.b, callable2.ab())