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 this
réfère à window
. Comment puis-je obtenir la référence à l'instance de classe lorsqu'elle est appelée en tant que fonction?
super(x)
(c'est-à-dire le transmettre àFunction
)? Je ne sais pas siFunction
peut être prolongé.Error
, entre autres.Function
s'agit simplement d'un constructeur de fonction. L'implémentation de la fonction doit être passée au constructeur. Si vous ne voulezSmth
pas accepter une implémentation, vous devez la fournir dans le constructeur, iesuper('function implementation here')
.Function
constructeur (runtime) qui est très différent d'une expression de fonction (syntaxe).Réponses:
L'
super
appel appellera leFunction
constructeur, 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: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
super
si vous ne le souhaitez pas - vous pouvez toujoursreturn
des objets arbitraires à partir de vos constructeurs de classe ES6. Dans ce cas, nous ferionsMais nous pouvons faire encore mieux et faire abstraction de cette chose
Smth
: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, utilisezmais notez que
Smth
cela n'héritera pas dynamiquement desFunction
propriétés statiques .la source
Smth
instances le soientinstanceof Smth
(comme tout le monde s'y attendrait). Vous pouvez omettre l'Object.setPrototypeOf
appel 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.Object.setPrototypeOf
n'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.this
etreturn
un objet.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:
Étendez cette classe et ajoutez une
__call__
méthode, plus ci-dessous ...Une explication en code et commentaires:
Voir sur repl.it
Explication supplémentaire de
bind
:function.bind()
fonctionne un peu commefunction.call()
, et ils partagent une signature de méthode similaire:fn.call(this, arg1, arg2, arg3, ...);
plus sur mdnfn.bind(this, arg1, arg2, arg3, ...);
plus sur mdnDans 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ùcall
appelle immédiatement la fonction avec les valeurs liées,bind
retourne un objet de fonction "exotique" qui enveloppe de manière transparente l'original, avecthis
et tous les arguments prédéfinis.Ainsi, lorsque vous définissez une fonction,
bind
certains de ses arguments:Vous appelez la fonction liée avec uniquement les arguments restants, son contexte est prédéfini, dans ce cas à
['hello']
.la source
bind
fonctionne (c'est-à-dire pourquoi il renvoie une instance deExFunc
)?bind
renvoie un objet de fonction transparent qui encapsule l'objet de fonction sur lequel il a été appelé, qui est notre objet appelable, juste avec lethis
rebond du contexte. Donc, il renvoie vraiment une instance deExFunc
. Message mis à jour avec plus d'informations surbind
.constructor
aprèsbind
enExFunc
. 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, viaSymbol.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.__call__
je ne peux pas accéderthis.a
outhis.ab()
. par exemple, repl.it/repls/FelineFinishedDesktopenvironmentVous pouvez envelopper l'instance Smth dans un proxy avec un
apply
(et peut-êtreconstruct
) un piège:la source
this
est toujours une fonction vide (chèquenew Smth().toString()
).setPrototypeOf
et ne dit rien sur les proxys. Mais je suppose que les procurations peuvent être aussi problématiques quesetPrototypeOf
. Et environtoString
, il peut être ombré avec une méthode personnalisée dansSmth.prototype
. Le natif dépend de toute façon de l'implémentation.construct
trap pour spécifier le comportement denew 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'atoString
noté Bergi.apply
mé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'informationsProxy
etReflect
de l'utiliser correctement?J'ai suivi les conseils de la réponse de Bergi et je l'ai enveloppé dans un module NPM .
la source
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
this
valeur duFunction
constructeur. 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
this
ne devient disponible qu'après la premièresuper
appel , c'est donc un peu délicat.Exemple de travail:
(L'exemple nécessite un navigateur moderne ou
node --harmony
.)Fondamentalement, la fonction de base
ClassFunction
extend encapsulera l'Function
appel du constructeur avec une fonction personnalisée qui est similaire à.bind
, mais autorise la liaison plus tard, au premier appel. Ensuite, dans leClassFunction
constructeur lui-même, il appelle la fonction retournée à partir desuper
laquelle est maintenant la fonction liée, en passantthis
pour terminer la configuration de la fonction de liaison personnalisée.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.
la source
bound
fera référence à la fonction que vous avezreturn
de 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).extends
ne semble pas vraiment fonctionner comme prévu, carFunction.isPrototypeOf(Smth)
etnew Smth instanceof Function
sont également faux.console.log((new Smth) instanceof Function);
esttrue
pour moi dans Node v5.11.0 et le dernier Firefox.new Smth instanceof Smth
que cela ne fonctionne pas avec votre solution. De plus, aucune méthode deSmth
ne sera disponible sur vos instances - car vous renvoyez simplement un standardFunction
, pas unSmth
.extend Function
rend égalementnew Smth instanceof Smth
faux.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.
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 outreapply
est apparue.Et le test, montrant que je suis capable de l'exécuter en tant que fonction de différentes manières:
Version avec
contient en fait des
bind
fonctionnalités:Version avec
rend
call
etapply
surwindow
incohérent:le chèque doit donc être déplacé dans
apply
:la source
this
est window, pas indéfini, donc la fonction créée n'est pas en mode strict (du moins en chrome).Function
n'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.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:
ExtensibleFunction
, le code est idiomatique d'étendre n'importe quelle classe ES6 (non, se faufiler avec des constructeurs ou des mandataires).instanceof
/.constructor
renvoie 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 luiExtensibleFunction
ou une sous-classe). Il utiliseObject.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.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:
Éditer
Comme j'étais d'humeur, je me suis dit que je publierais un package pour cela sur npm.
la source
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 :
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.
la source