Javascript: перегрузка оператора

Я работаю с JavaScript уже несколько дней и добрался до точки, где хочу перегрузить операторы для своих определенных объектов.

После ожидания поиска в Google, похоже, вы не можете официально это сделать, но есть несколько человек, которые заявляют о некотором длинном способе выполнения этого действия.

В основном я создал класс Vector2 и хочу иметь возможность сделать следующее:

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.

Вместо этого мне нужно сделать это:

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. 

Есть ли подход, который я могу предпринять для перегрузки операторов в моем классе Vector2? Поскольку это выглядит просто уродливо.

Ответы

Ответ 1

Как вы обнаружили, JavaScript не поддерживает перегрузку операторов. Самое близкое, что вы можете придумать, это реализовать toString (который будет вызываться, когда экземпляр должен быть приведен к строке), и valueOf (который будет вызываться для приведения его к числу, например, при использовании + для сложения или в во многих случаях при использовании его для конкатенации, потому что + пытается сделать сложение перед конкатенацией), что довольно ограничено. Ни один из них не позволяет вам создать объект Vector2 в результате.


Vector2 менее, для людей, которые приходят на этот вопрос и которым нужна строка или число в результате (вместо Vector2), здесь приведены примеры valueOf и toString. Эти примеры не демонстрируют перегрузку операторов, а только используют встроенную обработку JavaScript, преобразующую в примитивы:

valueOf

В этом примере удваивается значение свойства объекта val в ответ на приведение к примитиву, например, через +:

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)

Ответ 2

Как T.J. сказал, вы не можете перегружать операторов в JavaScript. Однако вы можете воспользоваться функцией valueOf, чтобы написать хак, который выглядит лучше, чем использование функций типа add каждый раз, но налагает ограничения на вектор, что x и y находятся между 0 и MAX_VALUE. Вот код:

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

Тогда вы можете написать уравнения следующим образом:

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]

Ответ 3

FYI paper.js решает эту проблему, создавая PaperScript, автономный, облачный javascript с перегрузкой операторов векторами, который затем обрабатывает обратно в javascript.

Но файлы документов должны быть специально указаны и обработаны как таковые.

Ответ 4

Можно сделать векторную математику с двумя числами, упакованными в одно. Позвольте мне сначала показать пример, прежде чем я объясню, как это работает:

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");

Я использую тот факт, что если вы сдвинете биты на два числа X раз, а затем сложите или вычтете их, прежде чем сдвинуть их обратно, вы получите тот же результат, как если бы вы не переместили их с самого начала. Аналогично скалярное умножение и деление работает симметрично для сдвинутых значений.

Число JavaScript имеет 52-битную целочисленную точность (64-разрядные числа с плавающей запятой), поэтому я упакую один номер в более высокий доступный 26-битный, а другой - в младший. Код сделан немного более грязным, потому что я хотел поддерживать числа со знаком.

vec_unpack = function(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;
    }
}

Единственный недостаток, который я могу видеть, это то, что x и y должны находиться в диапазоне +-33 миллионов, так как они должны умещаться в пределах 26 бит каждый.

Ответ 5

Моим любимым способом работы с векторами является создание массива, который содержит векторные клавиши, и цикл через этот массив, когда мне нужно использовать оба компонента моих векторов. Я не тестировал производительность этого подхода, но я уверен, что он более эффективен, чем популярные библиотеки, поскольку он не создает никаких объектов при выполнении вычислений и не выполняет никаких функций. Кроме того, это более читаемо.

function Vector(x, y) {
    this.x = x;
    this.y = y;
}

const comps = ['x', 'y'];
var d = new Vector(0, 0);
var v = new Vector(10, 0);
var t = 1;

for(let c of comps) {
    d[c] += v[c] * t;
}

console.log(d); //{ x: 10, y: 0 }

Ответ 6

На самом деле, есть один вариант JavaScript, который поддерживает перегрузку операторов. ExtendScript, язык сценариев, используемый приложениями Adobe, такими как Photoshop и Illustrator, действительно перегружен оператором. В нем вы можете написать:

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;

Более подробно это описано в "Руководстве по инструментам JavaScript Adobe Extendscript" (текущая ссылка здесь). Синтаксис, по-видимому, основан на (теперь давно заброшенном) проекте стандарта ECMAScript.