JavaScript meilleur façon de modifier le prototype de fonction

Je souhaite créer un constructeur de constructeurs. Relatif à ce fil: JavaScript construit un constructeur de constructeurs , il semble que les seules solutions sont:

Function.prototype.add = function(name, value) { this.prototype[name] = value; }; Function.prototype.remove = function(name) { delete this.prototype[name]; }; 

Mais je ne veux pas modifier le prototype de la Function générique … et aussi:

 var A = new ConstBuilder().add('test', function() { console.log('test'); }).getConstructor(); 

Mais je ne veux pas avoir d'enveloppe d'objet autour du constructeur lui-même.

Le problème est que généralement les constructeurs créent de nouveaux objets, héritant des méthodes du prototype du constructeur. Ce que j'essaie de faire est d'instancier les fonctions au lieu des objets, mais la seule façon de modifier une propriété prototype de fonction est celle de modifier sa propriété __proto__ :

 var constructorPrototype = { add : function(name, value) { this.prototype[name] = value ; } } ; var ConstBuilder = function() { var constructor = function() {} ; constructor.prototype = {} ; // The only way (?), but quite deprecated... constructor.__proto__ = constructorPrototype ; return constructor ; } ; // Not working way... //ConstBuilder.prototype = constructorPrototype ; var A = new ConstBuilder() ; A.add('test', function() { console.log('test') ; }) ; var a = new A() ; a.test() ; // "test" constructorPrototype.remove : function() { delete this.prototype[name] ; } ; A.remove('test') ; a.test() ; // Error: test is not a function. 

Notez que A.prototype n'est pas A.__proto__ mais A.prototype est (new A).__proto__ .

Et cela fonctionne parfaitement en modifiant __proto__ , quel dommage. J'ai lu que Firefox a intégré un "Object.setPrototypeOf ()", mais il est seulement expérimental.

Est-ce que ce serait une autre façon de faire ce que je veux faire?

Effectivement. La seule façon de faire ce que vous souhaitez faire est de muter la propriété __proto__ de la fonction que vous renvoyez. Cependant, ce n'est pas une mauvaise chose. En fait, ES6 Harmony va l'uniformiser en tant Object.setPrototypeOf fonction Object.setPrototypeOf .

Je vous conseillerais toutefois de ne pas modifier le [[Prototype]] d'un objet car il rend votre programme très lent . Il existe une alternative plus rapide disponible:

N'utilisez pas le prototype

Traditionnellement, le prototype est utilisé pour définir des fonctions qui fonctionnent sur un certain type d'objet. Ces fonctions, spécialisées sur un certain argument, s'appellent des méthodes.

Par exemple, obj.func(a, b, c) spécialise sur obj et les instances d' obj . D'autre part func(obj, a, b, c) ne se spécialise pas sur aucun argument (c.-à-d. obj peut être n'importe quelle valeur).

Suivant cet exemple, vous pouvez réécrire l' add et le remove comme suit:

 function add(func, name, value) { func.prototype[name] = value; } function remove(func, name) { delete func.prototype[name]; } 

Maintenant, vous pouvez utiliser add et remove sur n'importe quelle fonction que vous voulez. Vous ne devez pas vous soucier des héritages.

Le seul problème concerne les conflits d'espace de noms. Supposons que vous ayez déjà une fonction appelée add : what do you do? La réponse est plutôt simple. Vous créez un nouvel espace de noms:

 Function.add = function (func, name, value) { func.prototype[name] = value; }; Function.remove = function remove(func, name) { delete func.prototype[name]; }; 

En fait, c'est exactement ce que font habituellement les API JavaScript natives. Par exemple:

  1. Object.create
  2. Object.getPrototypeOf
  3. Object.setPrototypeOf

Etc., etc.

Le point est le suivant: la généralisation est toujours meilleure que la spécialisation. Nous utilisons des prototypes pour nous spécialiser. Nous utilisons des fonctions normales pour généraliser. Il existe de nombreux avantages de la généralisation par rapport à la spécialisation:

  1. Vous n'avez pas besoin de méthodes comme call et apply à des fonctions spécialisées «non spécialisées» .
  2. Vous n'avez pas à vous soucier de l'héritage et des chaînes de prototypes.
  3. Votre code est plus propre et plus facile à comprendre.

C'est la raison pour laquelle je préfère toujours la généralisation par rapport à la spécialisation. La seule raison pour laquelle j'ai jamais utilisé des prototypes est de créer des types d'union. Par exemple:

 function Shape(constructor) { this.constructor = constructor; } function Circle(x, y, r) { this.x = x; this.y = y; this.r = r; } function Rectangle(x1, y1, x2, y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } Circle.prototype = new Shape(Circle); Rectangle.prototype = new Shape(Rectangle); 

Au lieu d'ajouter des méthodes à Circle.prototype et Rectangle.prototype je fais ce qui suit à la place:

 Circle.area = function (circle) { return Math.PI * circle.r * circle.r; }; Rectangle.area = function (rectangle) { return Math.abs((rectangle.x2 - rectangle.x1) * (rectangle.y2 - rectangle.y1)); }; Shape.prototype.area = function () { return this.constructor.area(this); }; 

Maintenant, vous pouvez utiliser Circle.area(notCircleInstance) au lieu de Circle.prototype.area.call(notCircleInstance) . C'est une situation gagnant-gagnant. La généralisation est toujours meilleure que la spécialisation.