Surcharger les opérateurs arithmétiques en JavaScript?

C'est la meilleure façon de penser à formuler cette question, compte tenu de cette définition de «classe» de JavaScript:

var Quota = function(hours, minutes, seconds){ if (arguments.length === 3) { this.hours = hours; this.minutes = minutes; this.seconds = seconds; this.totalMilliseconds = Math.floor((hours * 3600000)) + Math.floor((minutes * 60000)) + Math.floor((seconds * 1000)); } else if (arguments.length === 1) { this.totalMilliseconds = hours; this.hours = Math.floor(this.totalMilliseconds / 3600000); this.minutes = Math.floor((this.totalMilliseconds % 3600000) / 60000); this.seconds = Math.floor(((this.totalMilliseconds % 3600000) % 60000) / 1000); } this.padL = function(val){ return (val.toString().length === 1) ? "0" + val : val; }; this.toString = function(){ return this.padL(this.hours) + ":" + this.padL(this.minutes) + ":" + this.padL(this.seconds); }; this.valueOf = function(){ return this.totalMilliseconds; }; }; 

Et le code de configuration de test suivant:

 var q1 = new Quota(23, 58, 50); var q2 = new Quota(0, 1, 0); var q3 = new Quota(0, 0, 10); console.log("Quota 01 is " + q1.toString()); // Prints "Quota 01 is 23:58:50" console.log("Quota 02 is " + q2.toString()); // Prints "Quota 02 is 00:01:00" console.log("Quota 03 is " + q3.toString()); // Prints "Quota 03 is 00:00:10" 

Existe-t-il un moyen de créer implicitement q4 comme objet Quota utilisant l'opérateur d'addition comme suit …

 var q4 = q1 + q2 + q3; console.log("Quota 04 is " + q4.toString()); // Prints "Quota 04 is 86400000" 

Plutôt que de recourir à …

 var q4 = new Quota(q1 + q2 + q3); console.log("Quota 04 is " + q4.toString()); // Prints "Quota 04 is 24:00:00" 

Sinon, quelles sont les meilleures recommandations de pratique dans ce domaine pour créer des objets JavaScript numériques personnalisés pouvant être composés via les opérateurs arithmétiques?

Pour autant que je sache, Javascript (au moins tel qu'il existe maintenant) ne prend pas en charge la surcharge de l'opérateur.

Le meilleur que je puisse suggérer est une méthode de classe pour créer de nouveaux objets de quota de plusieurs autres. Voici un exemple rapide de ce que je veux dire:

 // define an example "class" var NumClass = function(value){ this.value = value; } NumClass.prototype.toInteger = function(){ return this.value; } // Add a static method that creates a new object from several others NumClass.createFromObjects = function(){ var newValue = 0; for (var i=0; i<arguments.length; i++){ newValue += arguments[i].toInteger(); } return new this(newValue) } 

Et l'utiliser comme suit:

 var n1 = new NumClass(1); var n2 = new NumClass(2); var n3 = new NumClass(3); var combined = NumClass.createFromObjects(n1, n2, n3); 

Malheureusement non.

Pour les repli, si vous disposez des valeurs de retour, vous pouvez utiliser la méthode de chaînage

 var q4 = q1.plus(p2).plus(q3); 

Puisque tout le monde a voté sur mon autre réponse, je voulais publier un code de preuve de concept qui fonctionne en fait comme prévu.

Cela a été testé dans Chrome et IE.

 //Operator Overloading var myClass = function () { //Privates var intValue = Number(0), stringValue = String(''); //Publics this.valueOf = function () { if (this instanceof myClass) return intValue; return stringValue; } this.cast = function (type, call) { if (!type) return; if (!call) return type.bind(this); return call.bind(new type(this)).call(this); } } //Derived class var anotherClass = function () { //Store the base reference this.constructor = myClass.apply(this); var myString = 'Test', myInt = 1; this.valueOf = function () { if (this instanceof myClass) return myInt; return myString; } } //Tests var test = new myClass(), anotherTest = new anotherClass(), composed = test + anotherTest, yaComposed = test.cast(Number, function () { return this + anotherTest }), yaCComposed = anotherTest.cast(Number, function () { return this + test; }), t = test.cast(anotherClass, function () { return this + anotherTest }), tt = anotherTest.cast(myClass, function () { return this + test; }); debugger; 

Si quelqu'un serait tellement gentil de donner une explication technique POURQUOI ce n'est pas assez bon Je serais heureux de l'entendre!

Deuxième suggestion:

 var q4 = Quota.add(q1, q2, q3); 

Je suis récemment venu sur cet article: http://www.2ality.com/2011/12/fake-operator-overloading.html .

Il décrit comment redéfinir la méthode valueOf sur les objets pour faire quelque chose comme la surcharge de l'opérateur en javascript. Il semble que vous ne pouvez effectuer que des opérations de mutator sur les objets utilisés, de sorte qu'il ne ferait pas ce que vous voulez. C'est néanmoins intéressant.

Vous pouvez convertir implicitement en entier ou en chaîne, vos objets

Les objets ne sont implicitement convertis si JavaScript attend un nombre ou une chaîne. Dans le premier cas, la conversion prend trois étapes:

1.- Call valueOf (). Si le résultat est primitif (pas un objet), utilisez-le et convertissez-le en un nombre.

2. Sinon, appelez toString (). Si le résultat est primitif, utilisez-le et convertissez-le en un nombre.

3.- Sinon, lancez un TypeError. Exemple pour l'étape 1:

3 * {valueOf: function () {return 5}}

Si JavaScript convertit en chaîne, les étapes 1 et 2 sont échangées: toString () est essayé en premier, valueOf () second.

http://www.2ality.com/2013/04/quirk-implicit-conversion.html

Je ne suis pas sûr de savoir pourquoi les gens continuent de répondre à cette question avec non!

Il y a absolument un moyen que je décris avec un script très très petit que vous ne devez pas être John Resig pour comprendre …

Avant de le faire, je déclare également que, en JavaScript, la façon dont votre constructeur aurait travaillé est de vérifier les tableaux ou d'itérer le littéral «arguments».

Par exemple, dans mon constructeur de ma «classe», j'éloigne les roquettes, détermine le type des violences sous-jacentes et la traite intelligemment.

Cela signifie que si vous avez passé un tableau, je voudrais faire itérer les ruses pour trouver un tableau, puis iterate le tableau pour effectuer un traitement ultérieur en fonction du type de l'élément dans le tableau.

Par exemple -> new someClass ([instanceA, instanceB, instanceC])

Cependant, vous êtes en train de chercher une approche de style «C» plus supérieure à la surcharge de l'opérateur qui peut en fait être résolue contrairement à la croyance populaire.

Voici une classe que j'ai créée en utilisant MooTools qui fait honneur à la surcharge des opérateurs. Dans un ancien JavaScript, vous utiliserez simplement la même méthode toString qu'elle ne l'attache directement au prototype de l'instance.

Ma raison principale pour afficher cette approche est due au texte que je lis continuellement, ce qui indique que cette fonctionnalité est «impossible» à imiter. Rien n'est impossible à peine difficile et je vais afficher ceci ci-dessous …

  ////// debugger; //Make a counter to prove I am overloading operators var counter = 0; //A test class with a overriden operator var TestClass = new Class({ Implements: [Options, Events], stringValue: 'test', intValue: 0, initialize: function (options) { if (options && options instanceof TestClass) { //Copy or compose this.intValue += options.intValue; this.stringValue += options.stringValue; } else { this.intValue = counter++; } }, toString: function () { debugger; //Make a reference to myself var self = this; //Determine the logic which will handle overloads for like instances if (self instanceof TestClass) return self.intValue; //If this is not a like instance or we do not want to overload return the string value or a default. return self.stringValue; } }); //Export the class window.TestClass = TestClass; //make an instance var myTest = new TestClass(); //make another instance var other = new TestClass(); //Make a value which is composed of the two utilizing the operator overload var composed = myTest + other; //Make a value which is composed of a string and a single value var stringTest = '' + myTest; ////// 

L'affichage le plus récent de cette nomenclature a été observé sur la page de documentation de XDate: http://arshaw.com/xdate/

Dans ce cas, je crois que c'était vraiment plus facile, il aurait pu utiliser le prototype de l'objet Date pour atteindre la même chose.

Néanmoins, la méthode que j'ai donnée est un exemple qui devrait représenter ce style d'utilisation pour d'autres.

Modifier:

J'ai une implémentation complète ici:

http://netjs.codeplex.com/

Avec d'autres goodies.

J'ai fait un script qui effectue la surcharge de l'opérateur en JavaScript. Il ne s'agissait pas de faire du travail, mais il y a cependant quelques facettes. Je vais traverser les mises en garde ici à partir de la page du projet, sinon vous pouvez trouver le lien en bas:

  • Les résultats de calcul doivent être transmis à un nouvel objet, donc, au lieu de (p1 + p2 + p3), vous devez faire un nouveau point (p1 + p2 + p3), (étant donné que votre objet défini par l'utilisateur s'appelle "point").

  • Seulement +, -, * et / sont pris en charge, le cinquième arithmétique opperator% n'est pas. La coercition aux chaînes ("" + p1) et les comparaisons (p1 == p2) ne fonctionneront pas comme prévu. De nouvelles fonctions devraient être construites à ces fins si nécessaire, comme (p1.val == p2.val).

  • Enfin, les ressources de calcul nécessaires pour calculer la réponse augmentent de façon quadratique avec le nombre de termes. Par conséquent, seulement 6 termes sont autorisés dans une chaîne de calcul par défaut (même si cela peut être augmenté). Pour les chaînes de calcul plus longues, divisez les calculs, comme: nouveau point (nouveau point (p1 + p2 + p3 + p4 + p5 + p6) + nouveau point (p7 + p8 + p9 + p10 + p11 + p12))

La page Github .

En plus de ce qui a déjà été dit: la valeur valideOf () peut aider à produire une surcharge d'opérateurs assez puissante. Dans la preuve de concept, Fingers.js lib vous pouvez ajouter des auditeurs d'événements dans le style .NET:

 function hi() { console.log("hi") } function stackoverflow() { console.log("stackoverflow") } function bye() { console.log("bye") } on(yourButton).click += hi + stackoverflow; on(yourButton).click -= hi - bye; 

L'idée principale est de remplacer temporairement valueOf lorsque on s'appelle ():

 const extendedValueOf = function () { if (handlers.length >= 16) { throw new Error("Max 16 functions can be added/removed at once using on(..) syntax"); } handlers.push(this); // save current function return 1 << ((handlers.length - 1) * 2); // serialize it as a number. }; 

Le nombre renvoyé peut ensuite être désétalonné en fonction en utilisant le tableau des gestionnaires. De plus, il est possible d'extraire des valeurs de bit de la valeur finale (func1 + func2 – func3) de manière efficace, vous pouvez comprendre les fonctions ajoutées et les fonctions supprimées.

Vous pouvez vérifier la source sur github et jouer avec démo ici .

Une explication complète existe dans cet article (c'est pour AS3, difficile car c'est ecmascript, il fonctionnera aussi pour JS).

Pour certains cas d'utilisation limitée, vous pouvez avoir des effets de «surcharge» de l'opérateur:

 function MyIntyClass() { this.valueOf = function() { return Math.random(); } } var a = new MyIntyClass(); var b = new MyIntyClass(); a < b false a + b 0.6169137847609818 [a, b].sort() // O(n^2) ? [myClass, myClass] function MyStringyClass() { this.valueOf = function() { return 'abcdefg'[Math.floor(Math.random()*7)]; } } c = new MyStringyClass(); 'Hello, ' + c + '!' Hello, f! 

Le code ci-dessus est libre d'utiliser sous la licence MIT. YMMV.

Paper.js le fait, par exemple avec l'ajout de points ( docs ):

 var point = new Point(5, 10); var result = point + 20; console.log(result); // {x: 25, y: 30} 

Mais il le fait en utilisant son propre analyseur de script personnalisé .