Constructeur pour objets appelables en JavaScript

Comment puis-je créer un constructeur pour les objets appelables en JavaScript?

J'ai tenté diverses manières, comme la suite. L'exemple là-bas est l'exemple raccourci de l'objet réel.

function CallablePoint(x, y) { function point() { // Complex calculations at this point return point } point.x = x point.y = y return point } 

Cela fonctionne d'abord, mais l'objet qu'il crée n'est pas une instance de CallablePoint , donc il ne copie pas les propriétés de CallablePoint.prototype et dit false sur instanceof CallablePoint . Est-il possible de créer un constructeur de travail pour les objets appelables?

Il s'avère que c'est possible. Lorsque la fonction est créée, soit en utilisant la syntaxe de la function , soit sur le constructeur de Function , elle reçoit la propriété [[Call]] interne. Ce n'est pas une propriété de la fonction elle-même, mais plutôt une propriété que toute fonction obtient lors de sa construction.

Bien que cela ne signifie que n'importe quoi avec [[Call]] pourrait être uniquement Function lorsqu'il est construit (eh bien, il existe une exception – Function.prototype lui-même qui ne hérite pas de Function ), cela ne signifie pas qu'il ne peut pas devenir autre chose Plus tard, tout en conservant la propriété [[Call]] . Eh bien, à condition que votre navigateur ne soit pas IE < 11 .

Ce qui permet de changer la magie serait __proto__ de ES6, déjà implémenté dans de nombreux navigateurs. __proto__ est une propriété magique qui contient un prototype actuel. En le modifiant, je peux faire fonction qui hérite de quelque chose qui n'est pas Function .

 function CallablePoint(x, y) { function point() { // Complex calculations at this point return point } point.__proto__ = CallablePoint.prototype point.x = x point.y = y return point } // CallablePoint should inherit from Function, just so you could use // various function methods. This is not a requirement, but it's // useful. CallablePoint.prototype = Object.create(Function.prototype) 

Tout d'abord, le constructeur de CallablePoint fait une Function (seule la Function s est autorisée à commencer par la propriété [[Call]] . Ensuite, je modifie son prototype, donc il héritera CallablePoint . À ce stade, j'ai une fonction qui ne hérite pas De la Function (sorte de confusion).

Après avoir défini le constructeur pour CallablePoint s, j'ai configuré le prototype de CallablePoint en Function , alors j'ai CallablePoint qui hérite de Function .

De cette façon, les instances CallablePoint ont une chaîne d'héritage: CallablePoint -> Function -> Object , tout en étant toujours appelable. En outre, comme l'objet est appelable, il a selon la spécification, typeof égal à 'function' .

J'écrirai ma réponse en supposant que vous étiez après la fonctionnalité __call__ disponible en Python et souvent appelée «objet appelable». L'objet «appelable» semble étranger dans le contexte de JavaScript.

J'ai essayé plusieurs moteurs JavaScript, mais aucun de ceux que j'ai essayé vous permet d'appeler des objets, même si vous héritez de Function . Par exemple:

 function Callable(x) { ... "use strict"; ... this.__proto__ = Function.prototype; ... this.toString = function() { return x; }; ... } undefined > var c = new Callable(42); var c = new Callable(42); undefined > c; c; { toString: [function] } > c(42); c(42); TypeError: Property 'c' of object #<Object> is not a function at repl:1:1 at REPLServer.eval (repl.js:80:21) at repl.js:190:20 at REPLServer.eval (repl.js:87:5) at Interface.<anonymous> (repl.js:182:12) at Interface.emit (events.js:67:17) at Interface._onLine (readline.js:162:10) at Interface._line (readline.js:426:8) at Interface._ttyWrite (readline.js:603:14) at ReadStream.<anonymous> (readline.js:82:12) > c instanceof Function; c instanceof Function; true c.apply(null, [43]); TypeError: Function.prototype.apply was called on 43, which is a object and not a function at Function.APPLY_PREPARE (native) at repl:1:3 at REPLServer.eval (repl.js:80:21) at repl.js:190:20 at REPLServer.eval (repl.js:87:5) at Interface.<anonymous> (repl.js:182:12) at Interface.emit (events.js:67:17) at Interface._onLine (readline.js:162:10) at Interface._line (readline.js:426:8) at Interface._ttyWrite (readline.js:603:14) > 

C'est V8 (Node.js). C'est-à-dire que vous pouvez avoir un objet, qui hérite formellement de la fonction, mais il n'est pas appelable, et je n'ai pas trouvé un moyen de convaincre le temps d'exécution qu'il pourrait appeler. J'ai eu des résultats similaires dans la mise en œuvre de JavaScrip par Mozilla, alors je pense que cela doit être universel.

Cependant, le rôle des types personnalisés dans JavaScript est énormément faible, donc je ne pense pas que vous le manquerez beaucoup de toute façon. Mais, comme vous l'aviez déjà découvert, vous pouvez créer des propriétés sur les fonctions, de la même manière que possible sur les objets. Donc, vous pouvez le faire, de manière moins pratique.

Je ne sais pas si vous êtes conscient que votre objet ne sera qu'une instance de CallablePoint si vous utilisez le new mot-clé. En le nommant "callable", vous me faites penser que vous ne voulez pas utiliser de new . Quoi qu'il en soit, il est possible de renvoyer une instance (merci pour le conseil, Resig ):

 function CallablePoint(x, y) { if (this instanceof CallablePoint) { // Your "constructor" code goes here. // And don't return from here. } else { return new CallablePoint(x, y); } } 

Cela renverra une instance de CallablePoint peu importe comment elle s'appelait:

 var obj1 = CallablePoint(1,2); console.log(obj1 instanceof CallablePoint); // true var obj2 = new CallablePoint(1,2); console.log(obj2 instanceof CallablePoint); // true 

Si vous souhaitez que le constructeur CallablePoint() renvoie un objet du type CallablePoint, vous pouvez faire quelque chose comme ça où l'objet CallablePoint contient un point comme propriété de l'objet, mais reste un objet CallablePoint:

 function CallablePoint(x, y) { this.point = {}; this.point.x = x this.point.y = y } 

Ou si ce que vous essayez vraiment de faire est de créer une fonction qui vous renvoie un objet de CallablePoint, vous pouvez créer une fonction factorielle:

 function CallablePoint(x, y) { this.point = {}; this.point.x = x this.point.y = y } function makeCallablePoint(x, y) { return new CallablePoint(x,y); }