Sobrecargar Operadores Aritméticos en JavaScript?


Esta es la mejor manera que se me ocurre de formular esta pregunta, dada esta definición de "clase" 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;
    };
};

Y el siguiente código de configuración de prueba:

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"

¿Hay alguna forma de crear implícitamente q4 como un objeto Quota usando el operador de suma de la siguiente manera...

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

En lugar de recurrir a...

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

Si no, cuáles son las recomendaciones de mejores prácticas en esta área para hacer que los objetos JavaScript numéricos personalizados sean componibles a través de la aritmética los operadores?

Author: Peter McG, 2009-10-28

11 answers

Por lo que sé, Javascript (al menos como existe ahora) no admite la sobrecarga del operador.

Lo mejor que puedo sugerir es un método de clase para crear nuevos objetos de cuota a partir de varios otros. He aquí un ejemplo rápido de lo que quiero decir:

// 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)
}

Y úsalo como:

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

var combined = NumClass.createFromObjects(n1, n2, n3);
 33
Author: Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-28 01:22:22

Desafortunadamente no.

Para los fallbacks, si organizó los valores de retorno, podría usar el método encadenamiento

var q4 = q1.plus(p2).plus(q3);
 19
Author: Justin Love,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-28 01:19:46

Dado que todos votaron mi otra respuesta, quería publicar un código de prueba de concepto que, de hecho, funciona según lo previsto.

Esto ha sido probado en chrome e 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 alguien fuera tan amable de dar una explicación técnica POR QUÉ esto no es lo suficientemente bueno, ¡estaría feliz de escucharlo!

 13
Author: Jay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-09 01:18:49

Segunda sugerencia:

var q4 = Quota.add(q1, q2, q3);
 7
Author: Justin Love,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-28 01:20:27

Recientemente encontré este artículo: http://www.2ality.com/2011/12/fake-operator-overloading.html .

Describe cómo puede redefinir el método valueOf en objetos para hacer algo como la sobrecarga de operadores en javascript. Parece que solo puedes realizar operaciones de mutador en los objetos que se están operando, por lo que no haría lo que quieres. Es interesante sin embargo tho.

 5
Author: B T,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-29 20:34:41

Puede convertir implícitamente a entero o cadena, sus objetos

Los objetos solo se convierten implícitamente si JavaScript espera un número o una cadena. En el primer caso, la conversión toma tres pasos:

1.- Llamar a valueOf (). Si el resultado es primitivo (no un objeto), utilícelo y conviértalo en un número.

2.- De lo contrario, llame a toString(). Si el resultado es primitivo, utilícelo y conviértalo en un número.

3.- De lo contrario, lanza un error tipográfico. Ejemplo para step 1:

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

Si JavaScript se convierte en cadena, los pasos 1 y 2 se intercambian: toString() se intenta primero, valueOf() en segundo lugar.

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

 4
Author: Luis Naves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-23 02:39:55

Papel.js lo hace, por ejemplo con la adición de puntos (docs):

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

Pero lo hace usando su propio analizador de script personalizado.

 4
Author: Izhaki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-27 21:52:39

¡No estoy seguro de por qué la gente continúa respondiendo esta pregunta con un no!

Hay absolutamente una manera que describiré con un guión muy, muy pequeño que no tienes que ser John Resig para entender...

Antes de hacerlo, también indicaré que en JavaScript la forma en que su constructor habría funcionado es comprobando matrices o iterando el literal de 'argumentos'.

Por ejemplo, en mi constructor de mi 'clase' iteraría las arugments, determinaría el tipo de la arugments subyacentes y procesarlo inteligentemente.

Esto significa que si pasa un array, iteraría las arugments para encontrar un array y luego iteraría el array para hacer un procesamiento adicional dependiendo del tipo del elemento en el array.

Por ejemplo - > nueva clase ([instanceA, instanceB, instanceC])

Sin embargo, ustedes están buscando un enfoque de estilo más "C" para la sobrecarga del operador que en realidad puede ser achived contrario a la creencia popular.

Aquí hay una clase que he creado usando MooTools que hace honor operador sobrecarga. En JavaScript antiguo simplemente utilizaría el mismo método toString solo lo adjuntaría al prototipo de instancia directamente.

Mi principal razón para mostrar este enfoque es debido al texto que leo continuamente que dice que esta funcionalidad es "imposible" de emular. Nada es imposible solo suficientemente difícil y voy a mostrar esto a continuación...

 //////

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;

//////

La visualización más reciente de este la nomenclatura se observó en la página de documentación de XDate: http://arshaw.com/xdate /

En este caso creo que era en realidad aún más fácil, podría haber utilizado el prototipo del objeto de Fecha para lograr el mismo.

Sin embargo, el método que he dado como ejemplo que debe retratar este estilo de utilización para otros.

Editar:

Tengo una implementación completa aquí:

Http://netjs.codeplex.com/

Junto con otros golosina.

 2
Author: Jay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-22 19:58:53

Además de lo que ya se ha dicho: invalidar .valueOf () puede ayudar a producir una sobrecarga del operador bastante potente. En prueba de concepto Dedos.js lib puede agregar oyentes de eventos en estilo. 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;

La idea central es reemplazar temporalmente valueOf cuando se llama a on ():

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.
};

El número devuelto puede ser des-serializado de nuevo en función usando la matriz handlers. Además, es posible extraer valores de bits del valor final (func1 + func2 - func3) efectivamente, puede entender qué funciones se agregaron y qué funciones se eliminaron.

Puedes consultar la fuente en github y jugar con demo aquí.

Existe una explicación completa en este artículo (es para AS3, difícil ya que es ecmascript que funcionará para JS tampoco).

 1
Author: average Joe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-28 11:19:30

Hice un script que hace la sobrecarga del operador en JavaScript. No fue sencillo hacer el trabajo, así que hay algunas peculiaridades sin embargo. Voy a cruzar publicar las advertencias aquí desde la página del proyecto, de lo contrario se puede encontrar el enlace en la parte inferior:

  • Los resultados del cálculo deben pasarse a un nuevo objeto, por lo que en lugar de (p1 + p2 + p3) debe hacer un nuevo punto(p1 + p2 + p3), (dado que su objeto definido por el usuario se llama "punto").

  • Solo se admiten+, -, * y / , el quinto opperador aritmético % no lo es. La coerción a cadenas (""+p1) y las comparaciones (p1 == p2) no funcionarán como se esperaba. Nuevas funciones deben ser construidas para estos propósitos si es necesario, como (p1.val = = p2.val).

  • Finalmente, los recursos computacionales necesarios para calcular la respuesta aumentan cuadráticamente con el número de términos. Por lo tanto, solo se permiten 6 términos en una cadena de cálculo por defecto (aunque esto se puede aumentar). Para cadenas de cálculo más largas que eso, dividir los cálculos hacia arriba, como: nuevo punto(nuevo punto (p1 + p2 + p3 + p4 + p5 + p6) + nuevo punto ( p7 + p8 + p9 + p10 + p11 + p12))

La página de Github.

 1
Author: Stuffe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-18 16:41:17

Para algunos casos de uso limitados, puede tener efectos de "sobrecarga" del operador:

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!

El código anterior es de uso libre bajo la licencia MIT. YMMV.

 -1
Author: Jason Boyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-23 10:21:23