Comment étendre la fonction avec les classes ES6?

105

ES6 permet d'étendre des objets spéciaux. Il est donc possible d'hériter de la fonction. Un tel objet peut être appelé en tant que fonction, mais comment puis-je implémenter la logique d'un tel appel?

class Smth extends Function {
  constructor (x) {
    // What should be done here
    super();
  }
}

(new Smth(256))() // to get 256 at this call?

Toute méthode de classe obtient une référence à l'instance de classe via this. Mais quand il est appelé en tant que fonction, se thisréfère à window. Comment puis-je obtenir la référence à l'instance de classe lorsqu'elle est appelée en tant que fonction?

PS: Même question en russe.

Qwertiy
la source
17
Ah, enfin quelqu'un a posé cette question :-)
Bergi
1
Juste faire super(x)(c'est-à-dire le transmettre à Function)? Je ne sais pas si Functionpeut être prolongé.
Felix Kling
Gardez à l'esprit que l'extension des classes intégrées pose toujours des problèmes. La spécification suggère que cela devrait être possible, mais j'ai rencontré des problèmes d'extension Error, entre autres.
ssube
1
Gardez à l'esprit qu'il Functions'agit simplement d'un constructeur de fonction. L'implémentation de la fonction doit être passée au constructeur. Si vous ne voulez Smthpas accepter une implémentation, vous devez la fournir dans le constructeur, ie super('function implementation here').
Felix Kling
1
@Qwertiy: Je dirais que c'est l' exception , pas le cas général. Ceci est également très spécifique aux expressions de fonction , mais vous utilisez le Functionconstructeur (runtime) qui est très différent d'une expression de fonction (syntaxe).
Felix Kling

Réponses:

49

L' superappel appellera le Functionconstructeur, qui attend une chaîne de code. Si vous souhaitez accéder aux données de votre instance, vous pouvez simplement les coder en dur:

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

mais ce n'est pas vraiment satisfaisant. Nous voulons utiliser une fermeture.

Avoir la fonction retournée comme une fermeture pouvant accéder à vos variables d' instance est possible, mais pas facile. La bonne chose est que vous n'avez pas besoin d'appeler supersi vous ne le souhaitez pas - vous pouvez toujours returndes objets arbitraires à partir de vos constructeurs de classe ES6. Dans ce cas, nous ferions

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

Mais nous pouvons faire encore mieux et faire abstraction de cette chose 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;
  }
}

Certes, cela crée un niveau supplémentaire d'indirection dans la chaîne d'héritage, mais ce n'est pas forcément une mauvaise chose (vous pouvez l'étendre au lieu du natif Function). Si vous voulez l'éviter, utilisez

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

mais notez que Smthcela n'héritera pas dynamiquement des Functionpropriétés statiques .

Bergi
la source
Je veux accéder à l'état de classe à partir de la fonction.
Qwertiy
2
@Qwertiy: Ensuite, utilisez la deuxième suggestion de Bergi.
Felix Kling
@ AlexanderO'Mara: Vous ne pouvez pas muter le prototype de la fonction si vous voulez que vos Smthinstances le soient instanceof Smth(comme tout le monde s'y attendrait). Vous pouvez omettre l' Object.setPrototypeOfappel si vous n'avez pas besoin de cette méthode ou de l'une de vos méthodes prototypes déclarées dans votre classe.
Bergi
@ AlexanderO'Mara: Ce Object.setPrototypeOfn'est pas non plus un danger d'optimisation tant que cela est fait juste après la création de l'objet. C'est juste si vous mutez le [[prototype]] d'un objet dans les deux sens pendant sa durée de vie qu'il sera mauvais.
Bergi
1
@amn Non, vous ne le faites pas, quand vous n'utilisez pas thisetreturn un objet.
Bergi le
32

Il s'agit d'une approche permettant de créer des objets appelables qui référencent correctement leurs membres d'objet et conservent un héritage correct, sans jouer avec les prototypes.

Simplement:

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];
  }
}

Étendez cette classe et ajoutez une __call__méthode, plus ci-dessous ...

Une explication en code et commentaires:

// 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);  // true
console.log(callable2 instanceof ExFunc);  // true
console.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 access
console.log(callable2.a, callable2.b, callable2.ab())

Voir sur repl.it

Explication supplémentaire de bind:

function.bind()fonctionne un peu comme function.call(), et ils partagent une signature de méthode similaire:

fn.call(this, arg1, arg2, arg3, ...);plus sur mdn

fn.bind(this, arg1, arg2, arg3, ...);plus sur mdn

Dans les deux cas, le premier argument redéfinit le this contexte à l'intérieur de la fonction. Des arguments supplémentaires peuvent également être liés à une valeur. Mais où callappelle immédiatement la fonction avec les valeurs liées, bindretourne un objet de fonction "exotique" qui enveloppe de manière transparente l'original, avec thiset tous les arguments prédéfinis.

Ainsi, lorsque vous définissez une fonction, bindcertains de ses arguments:

var foo = function(a, b) {
  console.log(this);
  return a * b;
}

foo = foo.bind(['hello'], 2);

Vous appelez la fonction liée avec uniquement les arguments restants, son contexte est prédéfini, dans ce cas à ['hello'].

// We pass in arg `b` only because arg `a` is already set.
foo(2);  // returns 4, logs `['hello']`
Adrien
la source
Pouvez-vous s'il vous plaît ajouter une explication pourquoi bindfonctionne (c'est-à-dire pourquoi il renvoie une instance de ExFunc)?
Bergi
@Bergi bindrenvoie un objet de fonction transparent qui encapsule l'objet de fonction sur lequel il a été appelé, qui est notre objet appelable, juste avec le thisrebond du contexte. Donc, il renvoie vraiment une instance de ExFunc. Message mis à jour avec plus d'informations sur bind.
Adrien
1
@Bergi Tous les getters / setters et les méthodes sont accessibles, les propriétés / attributs doivent être attribués dans le constructoraprès binden ExFunc. Dans les sous-classes d'ExFunc, tous les membres sont accessibles. Quant à instanceof; dans es6, les fonctions liées sont qualifiées d'exotiques, donc leur fonctionnement interne n'est pas apparent, mais je pense qu'il transmet l'appel à sa cible enveloppée, via Symbol.hasInstance. Cela ressemble beaucoup à un proxy, mais c'est une méthode simple pour obtenir l'effet souhaité. Leur signature est similaire pas la même.
Adrien
1
@Adrien mais de l'intérieur __call__je ne peux pas accéder this.aou this.ab(). par exemple, repl.it/repls/FelineFinishedDesktopenvironment
rob
1
@rob bien repéré, il y a une erreur de référence, j'ai mis à jour la réponse et le code avec un correctif et une nouvelle explication.
Adrien
20

Vous pouvez envelopper l'instance Smth dans un proxy avec un apply(et peut-être construct) un piège:

class Smth extends Function {
  constructor (x) {
    super();
    return new Proxy(this, {
      apply: function(target, thisArg, argumentsList) {
        return x;
      }
    });
  }
}
new Smth(256)(); // 256
Oriol
la source
Bonne idée. Comme ça. Dois-je implémenter une logique supplémentaire au lieu de placer à l'intérieur de apply?
Qwertiy
4
Un proxy entraînerait des frais généraux, n'est-ce pas? En outre, thisest toujours une fonction vide (chèque new Smth().toString()).
Bergi
2
@Bergi Aucune idée de la performance. MDN a un gros avertissement en gras rouge setPrototypeOfet ne dit rien sur les proxys. Mais je suppose que les procurations peuvent être aussi problématiques que setPrototypeOf. Et environ toString, il peut être ombré avec une méthode personnalisée dans Smth.prototype. Le natif dépend de toute façon de l'implémentation.
Oriol
@Qwertiy Vous pouvez ajouter un constructtrap pour spécifier le comportement de new new Smth(256)(). Et ajoutez des méthodes personnalisées qui masquent les méthodes natives qui accèdent au code d'une fonction, comme l'a toStringnoté Bergi.
Oriol
Est-ce que votre applyméthode est implémentée de la façon dont elle est censée être utilisée, ou ce n'est qu'une démonstration et j'ai besoin de chercher plus d'informations Proxyet Reflectde l'utiliser correctement?
Qwertiy
3

J'ai suivi les conseils de la réponse de Bergi et je l'ai enveloppé dans un module NPM .

var CallableInstance = require('callable-instance');

class ExampleClass extends CallableInstance {
  constructor() {
    // CallableInstance accepts the name of the property to use as the callable
    // method.
    super('instanceMethod');
  }

  instanceMethod() {
    console.log("instanceMethod called!");
  }
}

var test = new ExampleClass();
// Invoke the method normally
test.instanceMethod();
// Call the instance itself, redirects to instanceMethod
test();
// The instance is actually a closure bound to itself and can be used like a
// normal function.
test.apply(null, [ 1, 2, 3 ]);
Ryan Patterson
la source
3

Mettre à jour:

Malheureusement, cela ne fonctionne pas tout à fait car il renvoie maintenant un objet fonction au lieu d'une classe, il semble donc que cela ne puisse pas être fait sans modifier le prototype. Boiteux.


Fondamentalement, le problème est qu'il n'y a aucun moyen de définir la thisvaleur du Functionconstructeur. La seule façon de vraiment faire cela serait d'utiliser le.bind méthode par la suite, mais ce n'est pas très convivial pour les classes.

Nous pourrions le faire dans une classe de base d'assistance, mais thisne devient disponible qu'après la premièresuper appel , c'est donc un peu délicat.

Exemple de travail:

'use strict';

class ClassFunction extends function() {
    const func = Function.apply(null, arguments);
    let bound;
    return function() {
        if (!bound) {
            bound = arguments[0];
            return;
        }
        return func.apply(bound, arguments);
    }
} {
    constructor(...args) {
        (super(...args))(this);
    }
}

class Smth extends ClassFunction {
    constructor(x) {
        super('return this.x');
        this.x = x;
    }
}

console.log((new Smth(90))());

(L'exemple nécessite un navigateur moderne ou node --harmony .)

Fondamentalement, la fonction de base ClassFunctionextend encapsulera l' Functionappel du constructeur avec une fonction personnalisée qui est similaire à .bind, mais autorise la liaison plus tard, au premier appel. Ensuite, dans le ClassFunctionconstructeur lui-même, il appelle la fonction retournée à partir de superlaquelle est maintenant la fonction liée, en passant thispour terminer la configuration de la fonction de liaison personnalisée.

(super(...))(this);

Tout cela est assez compliqué, mais cela évite de muter le prototype, qui est considéré comme de mauvaise forme pour des raisons d'optimisation et peut générer des avertissements dans les consoles de navigateur.

Alexander O'Mara
la source
1
Vous compliquez trop les choses. boundfera référence à la fonction que vous avez returnde cette classe anonyme. Nommez-le simplement et faites-y référence directement. Je recommanderais également d'éviter de passer des chaînes de code, ce n'est qu'un désordre avec lequel travailler (à chaque étape du processus de développement).
Bergi
Cette extends ne semble pas vraiment fonctionner comme prévu, car Function.isPrototypeOf(Smth)et new Smth instanceof Functionsont également faux.
Bergi
@Bergi Quel moteur JS utilisez-vous? console.log((new Smth) instanceof Function);esttrue pour moi dans Node v5.11.0 et le dernier Firefox.
Alexander O'Mara
Oups, mauvais exemple. C'est new Smth instanceof Smthque cela ne fonctionne pas avec votre solution. De plus, aucune méthode de Smthne sera disponible sur vos instances - car vous renvoyez simplement un standard Function, pas un Smth.
Bergi
1
@Bergi Darn, on dirait que vous avez raison. Cependant, l'extension de tous les types natifs semble poser le même problème.extend Functionrend également new Smth instanceof Smthfaux.
Alexander O'Mara
1

Tout d'abord, j'ai trouvé une solution avec arguments.callee, mais c'était horrible.
Je m'attendais à ce qu'il se brise en mode global strict, mais il semble que cela fonctionne même là-bas.

class Smth extends Function {
  constructor (x) {
    super('return arguments.callee.x');
    this.x = x;
  }
}

(new Smth(90))()

C'était une mauvaise façon de l'utiliser arguments.callee, de passer le code sous forme de chaîne et de forcer son exécution en mode non strict. Mais l'idée de passer outre applyest apparue.

var global = (1,eval)("this");

class Smth extends Function {
  constructor(x) {
    super('return arguments.callee.apply(this, arguments)');
    this.x = x;
  }
  apply(me, [y]) {
    me = me !== global && me || this;
    return me.x + y;
  }
}

Et le test, montrant que je suis capable de l'exécuter en tant que fonction de différentes manières:

var f = new Smth(100);

[
f instanceof Smth,
f(1),
f.call(f, 2),
f.apply(f, [3]),
f.call(null, 4),
f.apply(null, [5]),
Function.prototype.apply.call(f, f, [6]),
Function.prototype.apply.call(f, null, [7]),
f.bind(f)(8),
f.bind(null)(9),
(new Smth(200)).call(new Smth(300), 1),
(new Smth(200)).apply(new Smth(300), [2]),
isNaN(f.apply(window, [1])) === isNaN(f.call(window, 1)),
isNaN(f.apply(window, [1])) === isNaN(Function.prototype.apply.call(f, window, [1])),
] == "true,101,102,103,104,105,106,107,108,109,301,302,true,true"

Version avec

super('return arguments.callee.apply(arguments.callee, arguments)');

contient en fait des bindfonctionnalités:

(new Smth(200)).call(new Smth(300), 1) === 201

Version avec

super('return arguments.callee.apply(this===(1,eval)("this") ? null : this, arguments)');
...
me = me || this;

rend callet applysur windowincohérent:

isNaN(f.apply(window, [1])) === isNaN(f.call(window, 1)),
isNaN(f.apply(window, [1])) === isNaN(Function.prototype.apply.call(f, window, [1])),

le chèque doit donc être déplacé dans apply:

super('return arguments.callee.apply(this, arguments)');
...
me = me !== global && me || this;
Qwertiy
la source
1
Qu'essayez-vous de faire?
Merci
2
Je pense que les cours sont toujours en mode strict: stackoverflow.com/questions/29283935/…
Alexander O'Mara
@ AlexanderO'Mara, au fait, thisest window, pas indéfini, donc la fonction créée n'est pas en mode strict (du moins en chrome).
Qwertiy
S'il vous plaît, arrêtez de rétrograder cette réponse. J'ai déjà écrit que c'est une mauvaise façon. Mais c'est vraiment une réponse - cela fonctionne à la fois dans FF et Chrome (ne pas avoir Edge à vérifier).
Qwertiy
Je suppose que cela fonctionne parce que ce Functionn'est pas en mode strict. Bien que terrible, il est intéressant de +1. Cependant, vous ne pourriez probablement pas marcher plus loin sur une chaîne.
Alexander O'Mara
1

C'est la solution que j'ai élaborée qui répond à tous mes besoins d'extension de fonctions et qui m'a très bien servi. Les avantages de cette technique sont:

  • Lors de l'extension ExtensibleFunction, le code est idiomatique d'étendre n'importe quelle classe ES6 (non, se faufiler avec des constructeurs ou des mandataires).
  • La chaîne de prototypes est conservée dans toutes les sous-classes et instanceof/ .constructorrenvoie les valeurs attendues.
  • .bind() .apply()et .call()tous fonctionnent comme prévu. Ceci est fait en remplaçant ces méthodes pour modifier le contexte de la fonction "interne" par opposition à la fonctionExtensibleFunction instance (ou à sa sous-classe).
  • .bind()renvoie une nouvelle instance du constructeur de fonctions (que ce soit lui ExtensibleFunctionou une sous-classe). Il utilise Object.assign()pour s'assurer que les propriétés stockées sur la fonction liée sont cohérentes avec celles de la fonction d'origine.
  • Les fermetures sont honorées et les fonctions fléchées continuent de maintenir le bon contexte.
  • La fonction "interne" est stockée via a Symbol, qui peut être masquée par des modules ou un IIFE (ou toute autre technique courante de privatisation des références).

Et sans plus tarder, le code:

// The Symbol that becomes the key to the "inner" function 
const EFN_KEY = Symbol('ExtensibleFunctionKey');

// Here it is, the `ExtensibleFunction`!!!
class ExtensibleFunction extends Function {
  // Just pass in your function. 
  constructor (fn) {
    // This essentially calls Function() making this function look like:
    // `function (EFN_KEY, ...args) { return this[EFN_KEY](...args); }`
    // `EFN_KEY` is passed in because this function will escape the closure
    super('EFN_KEY, ...args','return this[EFN_KEY](...args)');
    // Create a new function from `this` that binds to `this` as the context
    // and `EFN_KEY` as the first argument.
    let ret = Function.prototype.bind.apply(this, [this, EFN_KEY]);
    // For both the original and bound funcitons, we need to set the `[EFN_KEY]`
    // property to the "inner" function. This is done with a getter to avoid
    // potential overwrites/enumeration
    Object.defineProperty(this, EFN_KEY, {get: ()=>fn});
    Object.defineProperty(ret, EFN_KEY, {get: ()=>fn});
    // Return the bound function
    return ret;
  }

  // We'll make `bind()` work just like it does normally
  bind (...args) {
    // We don't want to bind `this` because `this` doesn't have the execution context
    // It's the "inner" function that has the execution context.
    let fn = this[EFN_KEY].bind(...args);
    // Now we want to return a new instance of `this.constructor` with the newly bound
    // "inner" function. We also use `Object.assign` so the instance properties of `this`
    // are copied to the bound function.
    return Object.assign(new this.constructor(fn), this);
  }

  // Pretty much the same as `bind()`
  apply (...args) {
    // Self explanatory
    return this[EFN_KEY].apply(...args);
  }

  // Definitely the same as `apply()`
  call (...args) {
    return this[EFN_KEY].call(...args);
  }
}

/**
 * Below is just a bunch of code that tests many scenarios.
 * If you run this snippet and check your console (provided all ES6 features
 * and console.table are available in your browser [Chrome, Firefox?, Edge?])
 * you should get a fancy printout of the test results.
 */

// Just a couple constants so I don't have to type my strings out twice (or thrice).
const CONSTRUCTED_PROPERTY_VALUE = `Hi, I'm a property set during construction`;
const ADDITIONAL_PROPERTY_VALUE = `Hi, I'm a property added after construction`;

// Lets extend our `ExtensibleFunction` into an `ExtendedFunction`
class ExtendedFunction extends ExtensibleFunction {
  constructor (fn, ...args) {
    // Just use `super()` like any other class
    // You don't need to pass ...args here, but if you used them
    // in the super class, you might want to.
    super(fn, ...args);
    // Just use `this` like any other class. No more messing with fake return values!
    let [constructedPropertyValue, ...rest] = args;
    this.constructedProperty = constructedPropertyValue;
  }
}

// An instance of the extended function that can test both context and arguments
// It would work with arrow functions as well, but that would make testing `this` impossible.
// We pass in CONSTRUCTED_PROPERTY_VALUE just to prove that arguments can be passed
// into the constructor and used as normal
let fn = new ExtendedFunction(function (x) {
  // Add `this.y` to `x`
  // If either value isn't a number, coax it to one, else it's `0`
  return (this.y>>0) + (x>>0)
}, CONSTRUCTED_PROPERTY_VALUE);

// Add an additional property outside of the constructor
// to see if it works as expected
fn.additionalProperty = ADDITIONAL_PROPERTY_VALUE;

// Queue up my tests in a handy array of functions
// All of these should return true if it works
let tests = [
  ()=> fn instanceof Function, // true
  ()=> fn instanceof ExtensibleFunction, // true
  ()=> fn instanceof ExtendedFunction, // true
  ()=> fn.bind() instanceof Function, // true
  ()=> fn.bind() instanceof ExtensibleFunction, // true
  ()=> fn.bind() instanceof ExtendedFunction, // true
  ()=> fn.constructedProperty == CONSTRUCTED_PROPERTY_VALUE, // true
  ()=> fn.additionalProperty == ADDITIONAL_PROPERTY_VALUE, // true
  ()=> fn.constructor == ExtendedFunction, // true
  ()=> fn.constructedProperty == fn.bind().constructedProperty, // true
  ()=> fn.additionalProperty == fn.bind().additionalProperty, // true
  ()=> fn() == 0, // true
  ()=> fn(10) == 10, // true
  ()=> fn.apply({y:10}, [10]) == 20, // true
  ()=> fn.call({y:10}, 20) == 30, // true
  ()=> fn.bind({y:30})(10) == 40, // true
];

// Turn the tests / results into a printable object
let table = tests.map((test)=>(
  {test: test+'', result: test()}
));

// Print the test and result in a fancy table in the console.
// F12 much?
console.table(table);

Éditer

Comme j'étais d'humeur, je me suis dit que je publierais un package pour cela sur npm.

Aaron Levine
la source
1

Il existe une solution simple qui tire parti des capacités fonctionnelles de JavaScript: passez la «logique» comme argument de fonction au constructeur de votre classe, attribuez les méthodes de cette classe à cette fonction, puis retournez cette fonction du constructeur comme résultat :

class Funk
{
    constructor (f)
    { let proto       = Funk.prototype;
      let methodNames = Object.getOwnPropertyNames (proto);
      methodNames.map (k => f[k] = this[k]);
      return f;
    }

    methodX () {return 3}
}

let myFunk  = new Funk (x => x + 1);
let two     = myFunk(1);         // == 2
let three   = myFunk.methodX();  // == 3

Ce qui précède a été testé sur Node.js 8.

Un inconvénient de l'exemple ci-dessus est qu'il ne prend pas en charge les méthodes héritées de la chaîne de superclasses. Pour soutenir cela, remplacez simplement "Object. GetOwnPropertyNames (...)" par quelque chose qui renvoie également les noms des méthodes héritées. Comment faire cela, je crois, est expliqué dans une autre question-réponse sur Stack Overflow :-). BTW. Ce serait bien si ES7 ajoutait une méthode pour produire également les noms des méthodes héritées ;-).

Si vous avez besoin de prendre en charge les méthodes héritées, une possibilité est d'ajouter une méthode statique à la classe ci-dessus qui retourne tous les noms de méthodes hérités et locaux. Ensuite, appelez cela depuis le constructeur. Si vous étendez ensuite cette classe Funk, vous obtenez également cette méthode statique héritée.

Panu Logic
la source
Je pense que cet exemple donne une réponse simple à la question originale "... comment puis-je implémenter la logique d'un tel appel". Passez-le simplement en tant qu'argument à valeur fonction au constructeur. Dans le code ci-dessus, la classe Funk n'étend pas explicitement Function bien qu'elle le puisse, elle n'en a pas vraiment besoin. Comme vous pouvez le voir, vous pouvez appeler ses "instances" juts comme vous appelez n'importe quelle fonction ordinaire.
Panu Logic