Pracuję z JavaScript od kilku dni i doszedłem do punktu, w którym chcę przeciążać operatory dla moich zdefiniowanych obiektów.
Po krótkiej przerwie w wyszukiwarce Google wydaje się, że oficjalnie nie możesz tego zrobić, ale jest kilka osób, które twierdzą, że jest to długotrwały sposób wykonania tej czynności.
Zasadniczo stworzyłem klasę Vector2 i chcę móc wykonać następujące czynności:
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.
Zamiast tego muszę to zrobić:
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.
Czy istnieje podejście, które mogę zastosować w celu przeciążenia operatorów w mojej klasie Vector2? Ponieważ to wygląda po prostu brzydko.
javascript
operators
operator-overloading
Lee Brindley
źródło
źródło
Odpowiedzi:
Jak już wiesz, JavaScript nie obsługuje przeciążania operatorów. Najbliższe możliwe jest zaimplementowanie
toString
(które zostanie wywołane, gdy instancja będzie musiała zostać zmuszona do bycia ciągiem znaków) ivalueOf
(która zostanie wywołana, aby przekształcić ją w liczbę, na przykład podczas+
dodawania lub w wielu przypadkach, gdy używanie go do konkatenacji, ponieważ+
próbuje dodać przed konkatenacją), co jest dość ograniczone. Żaden z nich nie pozwalaVector2
w rezultacie utworzyć obiektu.Jednak dla osób przychodzących do tego pytania, które chcą w rezultacie otrzymać ciąg lub liczbę (zamiast a
Vector2
), oto przykładyvalueOf
itoString
. Te przykłady nie demonstrują przeciążenia operatorów, po prostu wykorzystują wbudowaną obsługę JavaScript do konwersji na prymitywy:valueOf
Ten przykład podwaja wartość
val
właściwości obiektu w odpowiedzi na wymuszenie na prymityw, na przykład poprzez+
:Pokaż fragment kodu
function Thing(val) { this.val = val; } Thing.prototype.valueOf = function() { // Here I'm just doubling it; you'd actually do your longAdd thing return this.val * 2; }; var a = new Thing(1); var b = new Thing(2); console.log(a + b); // 6 (1 * 2 + 2 * 2)
Lub z ES2015
class
:Pokaż fragment kodu
class Thing { constructor(val) { this.val = val; } valueOf() { return this.val * 2; } } const a = new Thing(1); const b = new Thing(2); console.log(a + b); // 6 (1 * 2 + 2 * 2)
Lub tylko z obiektami, bez konstruktorów:
Pokaż fragment kodu
var thingPrototype = { valueOf: function() { return this.val * 2; } }; var a = Object.create(thingPrototype); a.val = 1; var b = Object.create(thingPrototype); b.val = 2; console.log(a + b); // 6 (1 * 2 + 2 * 2)
toString
Ten przykład konwertuje wartość
val
właściwości obiektu na duże litery w odpowiedzi na wymuszenie na prymityw, na przykład poprzez+
:Pokaż fragment kodu
function Thing(val) { this.val = val; } Thing.prototype.toString = function() { return this.val.toUpperCase(); }; var a = new Thing("a"); var b = new Thing("b"); console.log(a + b); // AB
Lub z ES2015
class
:Pokaż fragment kodu
class Thing { constructor(val) { this.val = val; } toString() { return this.val.toUpperCase(); } } const a = new Thing("a"); const b = new Thing("b"); console.log(a + b); // AB
Lub tylko z obiektami, bez konstruktorów:
Pokaż fragment kodu
var thingPrototype = { toString: function() { return this.val.toUpperCase(); } }; var a = Object.create(thingPrototype); a.val = "a"; var b = Object.create(thingPrototype); b.val = "b"; console.log(a + b); // AB
źródło
Date
klasie niejawnie konwertują daty na liczby przy użyciuvalueOf
? Na przykład możesz to zrobićdate2 > date1
i będzie to prawdą, jeślidate2
został utworzony późniejdate1
.>
,<
,>=
, I<=
(ale nie==
,===
,!=
, lub!==
) używać Abstract relacyjny Porównanie działania, który korzystaToPrimitive
z podpowiedzi „numer”. NaDate
obiekcie daje togetTime
zwracaną liczbę (wartość milisekund od początku epoki).Jak powiedział TJ, nie możesz przeciążać operatorów w JavaScript. Możesz jednak skorzystać z
valueOf
funkcji, aby napisać hack, który wygląda lepiej niż używanie funkcji takich jak zaadd
każdym razem, ale nakłada na wektor ograniczenia, że xiy są między 0 a MAX_VALUE. Oto kod: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); };
Następnie możesz napisać takie równania:
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]
źródło
add
... Coś, czego nie chcieli zrobić.+
znakiem. To bardzo dobra odpowiedź pokazująca, jak uniknąć wywoływania nienaturalnej nazwy funkcji dla obiektów quasi-numerycznych.+
operatora jest możliwość zwrócenia aNumber
jako zamiennika jednego z operandów. Dlatego każda funkcja dodawania, która działa wObject
instancjach, musi zawsze zakodować obiekt jako aNumber
i ostatecznie go zdekodować.FYI paper.js rozwiązuje ten problem, tworząc PaperScript, samowystarczalny javascript o określonym zakresie z przeciążaniem operatorów wektorów, który następnie przetwarza z powrotem do javascript.
Ale pliki paperscript muszą być szczegółowo określone i przetworzone jako takie.
źródło
Właściwie istnieje jeden wariant JavaScript, który obsługuje przeciążanie operatorów. ExtendScript, język skryptowy używany przez aplikacje Adobe, takie jak Photoshop i Illustrator, ma przeciążenie operatorów. Możesz w nim napisać:
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;
Jest to opisane bardziej szczegółowo w „Przewodniku po narzędziach JavaScript Adobe Extendscript” (aktualne łącze tutaj ). Składnia najwyraźniej opierała się na (już dawno porzuconym) projekcie standardu ECMAScript.
źródło
Możliwe jest wykonanie matematyki wektorowej z dwiema liczbami spakowanymi w jedną. Najpierw pokażę przykład, zanim wyjaśnię, jak to działa:
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");
Korzystam z faktu, że jeśli przesuniesz nieco dwie liczby X razy, a następnie dodasz lub odejmiesz je przed przesunięciem z powrotem, otrzymasz ten sam wynik, jakbyś ich nie przesunął na początku. Podobnie mnożenie i dzielenie przez skalar działa symetrycznie dla przesuniętych wartości.
Liczba JavaScript ma 52 bity z dokładnością do liczb całkowitych (64-bitowe liczby zmiennoprzecinkowe), więc umieszczę jedną liczbę w wyższych dostępnych 26 bitach, a jedną w niższych. Kod jest trochę bardziej niechlujny, ponieważ chciałem obsługiwać podpisane numery.
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; } }
Jedynym minusem, jaki widzę w tym przypadku, jest to, że x i y muszą mieścić się w zakresie + -33 miliony, ponieważ muszą mieścić się w 26 bitach każdy.
źródło
Chociaż nie jest to dokładna odpowiedź na pytanie, możliwe jest zaimplementowanie niektórych metod __magic__ w Pythonie przy użyciu symboli ES6
[Symbol.toPrimitive]()
Metoda nie pozwala oznaczać wezwanieVector.add()
, ale pozwoli Ci używać składni takich jakDecimal() + 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; } } }
źródło
Możemy użyć hooków podobnych do Reacta, aby ocenić funkcję strzałki z różnymi wartościami z
valueOf
metody w każdej iteracji.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
Pokaż fragment kodu
const Vector2 = (function() { let index = -1 return function(x, y) { if (typeof x === 'function') { const calc = x index = 0, x = calc() index = 1, y = calc() index = -1 } return Object.assign([x, y], { valueOf() { return index == -1 ? this.toString() : this[index] }, toString() { return `[${this[0]}, ${this[1]}]` }, len() { return Math.sqrt(this[0] ** 2 + this[1] ** 2) } }) } })() const a = Vector2(1, 2) const b = Vector2(2, 4) console.log('a = ' + a) // a = [1, 2] console.log(`b = ${b}`) // b = [2, 4] const c = Vector2(() => (2 * a + b) / 2) // [2, 4] a[0] = 12 const d = Vector2(() => (2 * a + b) / 2) // [13, 4] const normalized = Vector2(() => d / d.len()) // [0.955..., 0.294...] console.log(c, d, normalized)
Library @ js-basics / vector używa tego samego pomysłu dla Vector3.
źródło
Ciekawe jest również eksperymentalne przeciążanie operatorów bibliotek -js . Przeładowuje tylko w określonym kontekście (funkcja wywołania zwrotnego).
źródło
Napisałem bibliotekę, która wykorzystuje kilka złych hacków, aby zrobić to w surowym JS. Pozwala na takie wyrażenia.
Liczby zespolone:
>> Complex()({r: 2, i: 0} / {r: 1, i: 1} + {r: -3, i: 2}))
<- {r: -2, i: 1}
Automatyczne różnicowanie:
Niech
f(x) = x^3 - 5x
:>> var f = x => Dual()(x * x * x - {x:5, dx:0} * x);
Teraz zmapuj to na niektóre wartości:
>> [-2,-1,0,1,2].map(a=>({x:a,dx:1})).map(f).map(a=>a.dx)
<- [ 7, -2, -5, -2, 7 ]
to znaczy
f'(x) = 3x^2 - 5.
Wielomiany:
>> Poly()([1,-2,3,-4]*[5,-6]).map((c,p)=>''+c+'x^'+p).join(' + ')
<- "5x^0 + -16x^1 + 27x^2 + -38x^3 + 24x^4"
W przypadku konkretnego problemu zdefiniowałbyś
Vector2
funkcję (lub może coś krótszego) za pomocą biblioteki, a następnie napisałx = Vector2()(x + y);
https://gist.github.com/pyrocto/5a068100abd5ff6dfbe69a73bbc510d7
źródło