Javascript: перевантаження оператора


93

Я працюю з 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
Щойно натрапив на оператор, що перевантажує бібліотеку. Ще не пробував і не знаю, наскільки це добре працює: google.com/…
fishinear

Відповіді:


102

Як ви виявили, JavaScript не підтримує перевантаження оператора. Найближче, до чого ви можете прийти, - це реалізація toString(яка буде викликана, коли екземпляр потрібно примусити бути рядком) і valueOf(який буде викликаний, щоб примусити його до числа, наприклад, коли використовується +для додавання, або у багатьох випадках, коли використання його для конкатенації, оскільки +намагається зробити додавання перед конкатенацією), що досить обмежено. Ні те, ні інше не дозволяє створити Vector2об’єкт як результат.


Для людей, які приходять до цього запитання і хочуть в результаті рядок або число (замість a Vector2), ось приклади valueOfта toString. Ці приклади не демонструють перевантаження оператора, лише скориставшись вбудованою обробкою JavaScript для перетворення на примітиви:

valueOf

Цей приклад подвоює значення властивості об'єкта valу відповідь на примус до примітиву, наприклад за допомогою +:

Або з ES2015 class:

Або просто з об’єктами, без конструкторів:

toString

Цей приклад перетворює значення властивості об'єкта у valверхній регістр у відповідь на примус до примітиву, наприклад за допомогою +:

Або з ES2015 class:

Або просто з об’єктами, без конструкторів:


1
Хоча це не підтримується у власне JS, в наш час досить поширено розширювати JS за допомогою спеціальних функцій і переводити назад до звичайного JS, наприклад, SweetJS націлений на вирішення саме цієї проблеми.
Дмитро Зайцев

1
Чи оператори порівняння Dateкласу неявно перетворюють дати в числа за допомогою valueOf? Наприклад, ви можете це зробити, date2 > date1і це буде правдою, якщо date2було створено після date1.
Шон Летендр,

1
@SeanLetendre: Так. >, <, >=, І <=(але не ==, ===, !=або !==) використовувати абстрактну реляційної порівнянні операцію, в якій використовується ToPrimitiveз підказкою «номером». Для Dateоб’єкта, що призводить до числа, яке getTimeповертається (значення мілісекунд з часу The-Epoch).
TJ Crowder,

23

Як сказав TJ, ви не можете перевантажувати оператори в 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]

7
Ви в основному щойно написали код для addметоду OP ... Те, що вони не хотіли робити.
Ian Brindley

15
@IanBrindley ОП хотів перевантажити оператора, що однозначно означає, що він планував написати таку функцію. Занепокоєння OP полягало в тому, щоб викликати "add", що є неприродно; математично ми представляємо додавання вектора зі +знаком. Це дуже хороша відповідь, яка показує, як уникнути виклику неприродної назви функції для квазічислових об’єктів.
Кітціль,

1
@Kittsil Питання показує, що я вже використовую функцію додавання. Хоча наведена вище функція взагалі не є поганою, вона не стосувалася питання, тому я погодився б з Йеном.
Lee Brindley

На сьогодні це єдино можливий спосіб. Єдина гнучкість, яку ми маємо з +оператором, - це можливість повернути a Numberяк заміну одному з операндів. Тому будь-яка функція додавання, яка працює Objectпримірниками, завжди повинна кодувати об’єкт як a Number, і врешті-решт декодувати його.
Гершом,

Зверніть увагу, що це поверне несподіваний результат (замість помилки) при множенні на два вектори. Також координати повинні бути цілими.
user202729

8

FYI paper.js вирішує цю проблему, створюючи PaperScript, автономний, обмежений javascript з операційним перевантаженням векторів, який потім обробляє назад у javascript.

Але файли paperScript мають бути конкретно вказані та оброблені як такі.


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;

Це детальніше описано в "Посібнику з інструментів Adobe Extendscript JavaScript" (поточне посилання тут ). Синтаксис, мабуть, базувався на (вже давно занедбаному) проекті стандарту ECMAScript.


9
ExtendScript! = JavaScript
Андріо ​​Скур

1
Чому відповідь ExtendScript відхилено, тоді як відповідь PaperScript? ІМХО ця відповідь також хороша.
xmedeko

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 бітів, а одне в нижнє. Код зроблений дещо безладнішим, тому що я хотів підтримати підписані номери.

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

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


Де визначення vec_pack?
Огидно

1
@ Огидний Хм, вибач, схоже, я забув додати, що ... Це тепер виправлено :)
Stuffe

3

Хоча це не точна відповідь на питання, можливо реалізувати деякі з методів python __magic__, використовуючи символи ES6

[Symbol.toPrimitive]()Метод не дозволяє на увазі виклик Vector.add(), але дозволить вам використовувати синтаксис , такі як 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

Цікавим є також експериментальна бібліотека operator-overloading-js . Це робить перевантаження лише у визначеному контексті (функція зворотного виклику).


2

Ми можемо використовувати React-подібні хуки для оцінки функції стрілки з різними значеннями від valueOfметоду на кожній ітерації.

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

Бібліотека @ js-basics / vector використовує ту саму ідею для Vector3.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.