Javascript: sovraccarico dell'operatore


93

Lavoro con JavaScript da alcuni giorni e sono arrivato al punto in cui voglio sovraccaricare gli operatori per i miei oggetti definiti.

Dopo un periodo su google alla ricerca di questo, sembra che tu non possa farlo ufficialmente, eppure ci sono alcune persone là fuori che rivendicano un modo prolisso di eseguire questa azione.

Fondamentalmente ho creato una classe Vector2 e voglio essere in grado di fare quanto segue:

var x = new Vector2(10,10);
var y = new Vector2(10,10);

x += y; //This does not result in x being a vector with 20,20 as its x & y values.

Invece devo fare questo:

var x = new Vector2(10,10);
var y = new Vector2(10,10);

x = x.add(y); //This results in x being a vector with 20,20 as its x & y values. 

C'è un approccio che posso adottare per sovraccaricare gli operatori nella mia classe Vector2? Poiché questo sembra semplicemente brutto.



1
Mi sono appena imbattuto in un operatore che sovraccaricava la libreria. Non l'ho provato e non so come funzioni bene, però: google.com/…
fishinear

Risposte:


102

Come hai scoperto, JavaScript non supporta il sovraccarico dell'operatore. Il massimo che puoi arrivare è implementare toString(che verrà chiamato quando l'istanza deve essere costretta a essere una stringa) e valueOf(che verrà chiamato per costringerlo a un numero, ad esempio quando si usa +per l'addizione, o in molti casi quando usandolo per la concatenazione perché +cerca di fare l'addizione prima della concatenazione), il che è piuttosto limitato. Nessuno dei due ti consente di creare un Vector2oggetto come risultato.


Per le persone che arrivano a questa domanda e che desiderano una stringa o un numero come risultato (anziché a Vector2), tuttavia, ecco alcuni esempi di valueOfe toString. Questi esempi non dimostrano il sovraccarico degli operatori, ma sfruttano solo il vantaggio della gestione incorporata di JavaScript che converte in primitive:

valueOf

Questo esempio raddoppia il valore della valproprietà di un oggetto in risposta a essere costretto a una primitiva, ad esempio tramite +:

O con ES2015 class:

O solo con oggetti, senza costruttori:

toString

Questo esempio converte il valore della valproprietà di un oggetto in lettere maiuscole in risposta a essere costretto a una primitiva, ad esempio tramite +:

O con ES2015 class:

O solo con oggetti, senza costruttori:


1
Sebbene non sia supportato all'interno di JS vero e proprio, è abbastanza comune in questi giorni estendere JS con funzionalità personalizzate e transpile di nuovo in JS semplice, ad esempio, SweetJS mira ad affrontare esattamente questo problema.
Dmitri Zaitsev

1
Gli operatori di confronto sulla Dateclasse convertono implicitamente le date in numeri usando valueOf? Ad esempio si può fare date2 > date1e sarà vero se è date2stato creato dopo date1.
Sean Letendre

1
@SeanLetendre: Sì. >, <, >=, E <=(ma non ==, ===, !=, o !==) utilizzare l'astratta relazionale confronto operazione, che utilizza ToPrimitivecon suggerimento "numero". Su un Dateoggetto, si ottiene il numero che getTimerestituisce (il valore dei millisecondi da The-Epoch).
TJ Crowder

23

Come ha detto TJ, non è possibile sovraccaricare gli operatori in JavaScript. Tuttavia puoi sfruttare la valueOffunzione per scrivere un hack che sembra migliore rispetto all'utilizzo di funzioni come addogni volta, ma impone i vincoli al vettore che xey sono compresi tra 0 e MAX_VALUE. Ecco il codice:

var MAX_VALUE = 1000000;

var Vector = function(a, b) {
    var self = this;
    //initialize the vector based on parameters
    if (typeof(b) == "undefined") {
        //if the b value is not passed in, assume a is the hash of a vector
        self.y = a % MAX_VALUE;
        self.x = (a - self.y) / MAX_VALUE;
    } else {
        //if b value is passed in, assume the x and the y coordinates are the constructors
        self.x = a;
        self.y = b;
    }

    //return a hash of the vector
    this.valueOf = function() {
        return self.x * MAX_VALUE + self.y;
    };
};

var V = function(a, b) {
    return new Vector(a, b);
};

Quindi puoi scrivere equazioni come questa:

var a = V(1, 2);            //a -> [1, 2]
var b = V(2, 4);            //b -> [2, 4]
var c = V((2 * a + b) / 2); //c -> [2, 4]

7
In pratica hai appena scritto il codice per il addmetodo dell'OP ... Qualcosa che non volevano fare.
Ian Brindley

15
@IanBrindley L'OP voleva sovraccaricare un operatore, il che implica chiaramente che aveva pianificato di scrivere una tale funzione. La preoccupazione di OP era di dover chiamare "add", che è innaturale; matematicamente, rappresentiamo l'aggiunta di vettore con un +segno. Questa è un'ottima risposta che mostra come evitare di chiamare un nome di funzione innaturale per oggetti quasi numerici.
Kittsil

1
@ Kittsil La domanda mostra che sto già utilizzando una funzione di aggiunta. Sebbene la funzione sopra non sia affatto una cattiva funzione, non ha affrontato la domanda, quindi sarei d'accordo con Ian.
Lee Brindley

Finora questo è l'unico modo possibile. L'unica flessibilità che abbiamo con l' +operatore è la possibilità di restituire un Numberin sostituzione di uno degli operandi. Pertanto, qualsiasi funzionalità aggiuntiva che funzioni le Objectistanze deve sempre codificare l'oggetto come a Number, e alla fine decodificarlo.
Gershom

Nota che questo restituirà un risultato inaspettato (invece di dare un errore) quando si moltiplicano due vettori. Anche le coordinate devono essere intere.
user202729

8

Cordiali saluti, paper.js risolve questo problema creando PaperScript, un javascript autonomo e con ambito con sovraccarico dell'operatore di vettori, che poi elabora di nuovo in javascript.

Ma i file paperscript devono essere specificatamente specificati ed elaborati come tali.


6

In realtà, v'è una variante di JavaScript che fa supporto all'operatore sovraccarico. ExtendScript, il linguaggio di scripting utilizzato dalle applicazioni Adobe come Photoshop e Illustrator, ha un sovraccarico dell'operatore. In esso puoi scrivere:

Vector2.prototype["+"] = function( b )
{
  return new Vector2( this.x + b.x, this.y + b.y );
}

var a = new Vector2(1,1);
var b = new Vector2(2,2);
var c = a + b;

Questo è descritto in maggior dettaglio nella "Guida agli strumenti JavaScript di Adobe Extendscript" ( link corrente qui ). La sintassi era apparentemente basata su una bozza (ora abbandonata da tempo) dello standard ECMAScript.


9
ExtendScript! = JavaScript
Andrio Skur

1
Perché la risposta di ExtendScript viene sottoposta a downvoting mentre la risposta di PaperScript viene upvotata? IMHO questa risposta è anche buona.
xmedeko

4

È possibile eseguire calcoli vettoriali con due numeri raggruppati in uno. Vorrei prima mostrare un esempio prima di spiegare come funziona:

let a = vec_pack([2,4]);
let b = vec_pack([1,2]);

let c = a+b; // Vector addition
let d = c-b; // Vector subtraction
let e = d*2; // Scalar multiplication
let f = e/2; // Scalar division

console.log(vec_unpack(c)); // [3, 6]
console.log(vec_unpack(d)); // [2, 4]
console.log(vec_unpack(e)); // [4, 8]
console.log(vec_unpack(f)); // [2, 4]

if(a === f) console.log("Equality works");
if(a > b) console.log("Y value takes priority");

Sto usando il fatto che se muovi due numeri X volte e poi li aggiungi o sottrai prima di spostarli indietro, otterrai lo stesso risultato come se non li avessi spostati all'inizio. Allo stesso modo la moltiplicazione e la divisione scalare funzionano in modo simmetrico per i valori spostati.

Un numero JavaScript ha 52 bit di precisione intera (64 bit in virgola mobile), quindi inserirò un numero nei 26 bit disponibili più alti e uno in quelli inferiori. Il codice è reso un po 'più disordinato perché volevo supportare i numeri firmati.

function vec_pack(vec){
    return vec[1] * 67108864 + (vec[0] < 0 ? 33554432 | vec[0] : vec[0]);
}

function vec_unpack(number){
    switch(((number & 33554432) !== 0) * 1 + (number < 0) * 2){
        case(0):
            return [(number % 33554432),Math.trunc(number / 67108864)];
        break;
        case(1):
            return [(number % 33554432)-33554432,Math.trunc(number / 67108864)+1];
        break;
        case(2):
            return [(((number+33554432) % 33554432) + 33554432) % 33554432,Math.round(number / 67108864)];
        break;
        case(3):
            return [(number % 33554432),Math.trunc(number / 67108864)];
        break;
    }
}

L'unico aspetto negativo che posso vedere con questo è che xey devono essere nell'intervallo + -33 milioni, poiché devono rientrare in 26 bit ciascuno.


Dov'è la definizione di vec_pack?
Disgustoso

1
@Disgusting Hmm scusa, sembra che avessi dimenticato di aggiungerlo ... Ora è stato risolto :)
Stuffe

3

Sebbene non sia una risposta esatta alla domanda, è possibile implementare alcuni dei metodi __magic__ python usando i simboli ES6

Un [Symbol.toPrimitive]()metodo non ti consente di implicare una chiamata Vector.add(), ma ti consente di utilizzare la sintassi come Decimal() + int.

class AnswerToLifeAndUniverseAndEverything {
    [Symbol.toPrimitive](hint) {
        if (hint === 'string') {
            return 'Like, 42, man';
        } else if (hint === 'number') {
            return 42;
        } else {
            // when pushed, most classes (except Date)
            // default to returning a number primitive
            return 42;
        }
    }
}


2

Possiamo usare Hooks React-like per valutare la funzione freccia con valori diversi dal valueOfmetodo su ogni iterazione.

const a = Vector2(1, 2) // [1, 2]
const b = Vector2(2, 4) // [2, 4]    
const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
// There arrow function will iterate twice
// 1 iteration: method valueOf return X component
// 2 iteration: method valueOf return Y component

Library @ js-basics / vector utilizza la stessa idea per Vector3.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.