web-dev-qa-db-fra.com

Surcharger les opérateurs arithmétiques en JavaScript?

C'est la meilleure façon dont je peux penser à formuler cette question, étant donné cette définition de "classe" 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 en tant qu'objet Quota en 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 pratiques recommandées dans ce domaine pour rendre les objets JavaScript numériques personnalisés composables via les opérateurs arithmétiques?

66
Peter McG

Pour autant que je sache, Javascript (du moins tel qu'il existe actuellement) ne prend pas en charge la surcharge des opérateurs.

Le mieux que je puisse suggérer est une méthode de classe pour créer de nouveaux objets quota à partir 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:

var n1 = new NumClass(1);
var n2 = new NumClass(2);
var n3 = new NumClass(3);

var combined = NumClass.createFromObjects(n1, n2, n3);
33
Dan

Malheureusement non.

Pour les solutions de repli, si vous avez organisé les valeurs de retour, vous pouvez utiliser le chaînage de méthode

var q4 = q1.plus(p2).plus(q3);
20
Justin Love

Étant donné que tout le monde a voté contre 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 était assez aimable pour donner une explication technique POURQUOI ce n'est pas assez bon, je serais heureux de l'entendre!

14
Jay

Deuxième suggestion:

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

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

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

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

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

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

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

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

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

5
Luis Naves

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

Il décrit comment vous pouvez redéfinir la méthode valueOf sur des objets pour faire quelque chose comme une surcharge d'opérateur en javascript. Il semble que vous ne puissiez vraiment effectuer des opérations de mutation que sur les objets opérés, il ne fera donc pas ce que vous voulez. C'est néanmoins intéressant.

5
B T

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é .

4
Izhaki

J'ai fait un script qui fait une surcharge d'opérateur en JavaScript. Ce n'était pas simple de travailler, donc il y a quelques bizarreries. Je vais croiser les mises en garde ici à partir de la page du projet, sinon vous pouvez trouver le lien en bas:

  • Les résultats du calcul doivent être passés à 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 est nommé "point").

  • Seuls +, -, * et/sont pris en charge, le cinquième opperateur arithmétique% ne l'est pas. La contrainte sur les 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, seuls 6 termes sont autorisés dans une chaîne de calcul par défaut (bien que cela puisse être augmenté). Pour des chaînes de calcul plus longues que cela, 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 .

3
Stuffe

Je ne sais pas pourquoi les gens continuent de répondre à cette question par non!

Il y a absolument un moyen que je vais décrire avec un très petit script que vous n'avez pas besoin d'être John Resig pour comprendre ...

Avant de le faire, je dirai également qu'en JavaScript, la façon dont votre constructeur aurait fonctionné est de vérifier les tableaux ou d'itérer le littéral "arguments".

par exemple. Dans mon constructeur de ma "classe", j'itérerais les arugments, déterminerais le type des arugments sous-jacents et les traiterais intelligemment.

Cela signifie que si vous transmettez un tableau, j'itère les arugments pour trouver un tableau, puis je réitère le tableau pour effectuer un traitement supplémentaire en fonction du type d'élément dans le tableau.

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

Cependant, vous êtes à la recherche d'une approche plus "C" de la surcharge de l'opérateur qui peut être réalisée contrairement à la croyance populaire.

Voici une classe que j'ai créée en utilisant MooTools qui respecte la surcharge d'opérateur. Dans le vieux JavaScript, vous utiliseriez simplement la même méthode toString que pour la joindre directement au prototype d'instance.

Ma principale raison pour afficher cette approche est à cause du texte que je lis continuellement qui déclare que cette fonctionnalité est "impossible" à émuler. Rien n'est impossible seulement suffisamment difficile et je vais afficher cela 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 pense que c'était en fait encore plus facile, il aurait pu utiliser le prototype de l'objet Date pour y parvenir.

Néanmoins, la méthode que j'ai donnée à titre d'exemple devrait décrire ce style d'utilisation pour les autres.

Modifier:

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

http://netjs.codeplex.com/

Avec d'autres goodies.

2
Jay

En plus de ce qui a déjà été dit: remplacer .valueOf () peut aider à produire une surcharge d'opérateur assez puissante. Dans la preuve de concept Fingers.js lib, vous pouvez ajouter des écouteurs 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 () est appelé:

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 retourné peut ensuite être désérialisé en fonction à l'aide du tableau de gestionnaires. De plus, il est possible d'extraire des valeurs binaires de la valeur finale (func1 + func2 - func3) afin que vous puissiez comprendre efficacement quelles fonctions ont été ajoutées et quelles fonctions ont été supprimées.

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

Une explication complète existe dans ce article (c'est pour AS3, difficile car c'est ecmascript cela fonctionnera pour JS non plus).

1
average Joe