Arithmetische Operatoren in JavaScript überladen?


72

Dies ist der beste Weg, diese Frage zu formulieren, wenn man diese JavaScript-Definition der "Klasse" berücksichtigt:

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

und den folgenden Test-Setup-Code:

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"

Gibt es eine Möglichkeit, mit dem Additionsoperator wie folgt implizit q4als QuotaObjekt zu erstellen ...

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

anstatt auf ... zurückzugreifen

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

Wenn nicht, was sind die Best-Practice-Empfehlungen in diesem Bereich, um benutzerdefinierte numerische JavaScript-Objekte über die arithmetischen Operatoren zusammensetzbar zu machen?


Schauen Sie sich SweetJS an, das genau das kann.
Dmitri Zaitsev


Antworten:


39

Soweit mir bekannt ist, unterstützt Javascript (zumindest so wie es jetzt existiert) das Überladen von Operatoren nicht.

Das Beste, was ich vorschlagen kann, ist eine Klassenmethode zum Erstellen neuer Kontingentobjekte aus mehreren anderen. Hier ist ein kurzes Beispiel dafür, was ich meine:

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

und benutze es wie:

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

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


1
Ich bin etwas verärgert darüber, dass eine Sprache, die negative Werte von negativen Operanden an ihren Moduloperator zurückgibt, das Überladen von Operatoren nicht unterstützen würde. Jeder auf der Welt muss zu diesem Zeitpunkt% als ((a% b) + b)% b implementieren
David Bandel

22

Unglücklicherweise nicht.

Wenn Sie für Fallbacks die Rückgabewerte angeordnet haben, können Sie die Methodenverkettung verwenden

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

5
Wenn Ihre Umgebung dies unterstützt, können Sie Currying auch für eine schönere API verwenden:one(two)(three)
ELLIOTTCABLE

4
@elliottcable Gutes + kluges Denken, aber während das für die Multiplikation in Ordnung sein könnte, denke ich nicht, dass es innerhalb der typischen Programmierer-Denkweise gut kommuniziert. Ich würde immer noch mitgehen one.times(two).times(three);.
Nobbynob Littlun

In CoffeeScript könnten Sie auch einige der Parens fallen lassen :)
Carl Smith

14

Da alle meine andere Antwort abgelehnt haben, wollte ich einen Proof-of-Concept-Code veröffentlichen, der tatsächlich wie beabsichtigt funktioniert.

Dies wurde in Chrome und IE getestet.

//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;

Wenn jemand so freundlich wäre, eine technische Erklärung zu geben, WARUM dies nicht gut genug ist, würde ich mich freuen, es zu hören!


1
Das Übergeben an einen neuen Typ in der Besetzung kann überschrieben werden, wenn dies vom abgeleiteten ....
Jay

6
Jay, kannst du zeigen, wie dies mit einer MyNumber-Klasse funktionieren würde, um Arithmetik zu machen (mach ein Beispiel)?
Trusktr

Funktioniert es nur, weil der Typ einen einzelnen int-Wert enthält?
Greenoldman


7

Sie können Ihre Objekte implizit in eine Ganzzahl oder eine Zeichenfolge konvertieren.

Objekte werden nur implizit konvertiert, wenn JavaScript eine Zahl oder eine Zeichenfolge erwartet. Im ersteren Fall erfolgt die Konvertierung in drei Schritten:

1.- Rufen Sie an valueOf(). Wenn das Ergebnis primitiv ist (kein Objekt), verwenden Sie es und konvertieren Sie es in eine Zahl.

2.- Andernfalls anrufen toString(). Wenn das Ergebnis primitiv ist, verwenden Sie es und konvertieren Sie es in eine Zahl.

3.- Andernfalls werfen Sie a TypeError. Beispiel für Schritt 1:

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

Wenn JavaScript in einen String konvertiert wird, werden die Schritte 1 und 2 ausgetauscht: toString () wird zuerst versucht, valueOf () zweitens.

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


6

Paper.js macht es zum Beispiel mit Punktaddition ( docs ):

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

Dies geschieht jedoch mithilfe eines eigenen benutzerdefinierten Skriptparsers .


Können Sie den Trick bitte mit einem kleinen Beispielcode erklären? Vielen Dank. Ich habe Beispielcode gesehen.
Et7f3XIV

Die +20 addieren 20 zu den Elementen x und y. Zeile 2
Et7f3XIV

2
Dieses Snippet wird mit dem bereitgestellten benutzerdefinierten Parser interpretiert - dies ist kein Standard-Javascript.
Izhaki

Ich habe den Aufruf dieses Parsers in Paper.js Beispiel nicht gesehen. Ist es wie eine sehr intelligente Bewertung?
Et7f3XIV

5

Ich bin kürzlich auf diesen Artikel gestoßen : http://www.2ality.com/2011/12/fake-operator-overloading.html .

Es wird beschrieben, wie Sie die valueOf-Methode für Objekte neu definieren können, um beispielsweise das Überladen von Operatoren in Javascript durchzuführen. Es scheint, dass Sie nur Mutator-Operationen an den Objekten ausführen können, an denen gearbeitet wird, sodass es nicht das tut, was Sie wollen. Trotzdem ist es interessant.


5

Ich habe ein Skript erstellt, das das Überladen von Operatoren in JavaScript ausführt. Es war nicht einfach, Arbeit zu machen, daher gibt es ein paar Macken. Ich werde die Vorbehalte hier auf der Projektseite kreuzen, andernfalls finden Sie den Link unten:

  • Berechnungsergebnisse müssen an ein neues Objekt übergeben werden, sodass Sie anstelle von (p1 + p2 + p3) einen neuen Punkt (p1 + p2 + p3) ausführen müssen (vorausgesetzt, Ihr benutzerdefiniertes Objekt heißt "Punkt").

  • Es werden nur +, -, * und / unterstützt, der fünfte arithmetische Opperator% nicht. Zwang zu Zeichenfolgen ("" + p1) und Vergleichen (p1 == p2) funktioniert nicht wie erwartet. Für diese Zwecke sollten bei Bedarf neue Funktionen erstellt werden, z. B. (p1.val == p2.val).

  • Schließlich steigt der Rechenaufwand für die Berechnung der Antwort quadratisch mit der Anzahl der Terme. Daher sind pro Standard nur 6 Begriffe in einer Berechnungskette zulässig (obwohl dies erhöht werden kann). Teilen Sie für längere Berechnungsketten die Berechnungen wie folgt auf: neuer Punkt (neuer Punkt (p1 + p2 + p3 + p4 + p5 + p6) + neuer Punkt (p7 + p8 + p9 + p10 + p11 + p12))

Die Github-Seite .


2

Ich bin mir nicht sicher, warum die Leute diese Frage weiterhin mit Nein beantworten!

Es gibt absolut einen Weg, den ich mit einem sehr sehr kleinen Skript skizzieren werde, das Sie nicht John Resig sein müssen, um zu verstehen ...

Bevor ich dies tue, werde ich auch feststellen, dass Ihr Konstruktor in JavaScript so funktioniert hätte, indem er nach Arrays gesucht oder das Literal 'Argumente' iteriert hätte.

zB würde ich in meinem Konstruktor meiner 'Klasse' die Arugments iterieren, den Typ der zugrunde liegenden Arugments bestimmen und sie intelligent verarbeiten.

Dies bedeutet, dass ich, wenn Sie ein Array übergeben würden, die Arugments iterieren würde, um ein Array zu finden, und dann das Array iterieren würde, um abhängig vom Typ des Elements im Array eine weitere Verarbeitung durchzuführen.

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

Ihr sucht jedoch nach einem Ansatz im C-Stil zur Überlastung von Bedienern, der entgegen der populären Überzeugung tatsächlich erreicht werden kann.

Hier ist eine Klasse, die ich mit MooTools erstellt habe und die das Überladen von Operatoren berücksichtigt. In einfachem altem JavaScript würden Sie nur dieselbe toString-Methode verwenden und sie nur direkt an den Prototyp der Instanz anhängen.

Mein Hauptgrund für die Anzeige dieses Ansatzes ist der Text, den ich ständig lese und der besagt, dass diese Funktionalität "unmöglich" zu emulieren ist. Nichts ist unmöglich, nur ausreichend schwierig, und ich werde dies unten anzeigen ...

 //////

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;

//////

Die letzte Anzeige dieser Nomenklatur wurde auf der Dokumentationsseite von XDate beobachtet: http://arshaw.com/xdate/

In diesem Fall, glaube ich, war es sogar noch einfacher, er hätte den Prototyp des Date-Objekts verwenden können, um dasselbe zu erreichen.

Nichtsdestotrotz habe ich die Methode als Beispiel angegeben, die diesen Nutzungsstil für andere darstellen soll.

Bearbeiten:

Ich habe hier eine vollständige Implementierung:

http://netjs.codeplex.com/

Zusammen mit anderen Leckereien.


6
Was Sie hier gezeigt haben, ist keine Überladung des Bedieners. Außerdem in Ihrem Beispiel toString()kehrt '0'zu stringTest. Sie sollten valueOf()für die Rücksendung eines Nummernersatzes verwenden (mehr davon hier: adäquatgood.com /2010 / 3/… ). Dies ist jedoch nur ein Wertersatz. Dies ist keine Funktion zum Überladen von Operatoren. Selbst mit Ihrer Methode kann ich keine Klasse implementieren, Vectordie Felder subtrahieren würde, .xund .ywenn mir das gefällt : vectorC = vectorA - vectorB. Dazu benötigen Sie eine Überlastung des Bedieners, die in ES5 nicht möglich ist.
pepkin88

Ich habe festgestellt, dass Sie mit einer Kombination der oben genannten Taktiken mit function.bind steuern können, welche Version der Methode aufgerufen wird und sie funktioniert, obwohl sie nicht so flexibel ist wie in anderen Sprachen ... ZB können Sie eine erstellen .cast-Funktion, die ein Objekt nimmt und die Methode eines anderen Typs aufruft, wobei der Kontext auf das angegebene Objekt festgelegt ist. Trotzdem ist dies nicht dasselbe wie in anderen Sprachen, aber ich argumentiere immer noch, dass es funktionieren kann: P
Jay

@ Jay Ich habe dies in meinem Browser und in Node.js versucht und es beschwert sich, dass Class nicht definiert ist, also funktioniert es nicht ......... Oh, warte, ich sehe: Class ist von MooTools. Können Sie diese Arbeit mit jQuery machen? Oder noch besser, ohne Bibliothek, nur JavaScript?
Trusktr

2

Zusätzlich zu dem, was bereits gesagt wurde: Das Überschreiben von .valueOf () kann zu einer recht leistungsstarken Überladung des Operators führen. In der Proof-of-Concept- Bibliothek von Fingers.js können Sie Ereignis-Listener im .NET-Stil hinzufügen:

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;

Die Kernidee besteht darin, valueOf vorübergehend zu ersetzen, wenn on () aufgerufen wird:

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

Die zurückgegebene Nummer kann dann mithilfe des Handler-Arrays wieder in die Funktion de-serialisiert werden. Darüber hinaus ist es möglich, Bitwerte aus dem Endwert (func1 + func2 - func3) zu extrahieren, damit Sie effektiv verstehen können, welche Funktionen hinzugefügt und welche Funktionen entfernt wurden.

Sie können die Quelle auf überprüfen Github überprüfen und hier mit der Demo spielen .

In diesem Artikel finden Sie eine vollständige Erklärung (es ist für AS3, schwierig, da es sich um ein Ecmascript handelt, das auch für JS funktioniert).


-2

Für einige eingeschränkte Anwendungsfälle können Operator-Überladungseffekte auftreten:

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!

Der obige Code kann kostenlos unter der MIT-Lizenz verwendet werden. YMMV.


10
Kostenlose Nutzung unter der MIT-Lizenz? Ich glaube nicht, dass Sie verstehen, worum es auf dieser Website geht.
Aran Mulholland

3
@AranMulholland Hast du? Die aktuelle SE-Lizenz ist CC BY-SA (war es schon immer) und sie planen, zu einer Art MIT meta.stackexchange.com/questions/272956/… zu wechseln
Mario Trucco

1
Wenn ich das richtig verstehe, überlastet dies den Operator nicht, sondern delegiert ihn einfach an seine ursprüngliche Implementierung. Also sehr begrenzte Verwendung, wie Sie betonen.
Dmitri Zaitsev
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.