Javascript: operator kelebihan beban

95

Saya telah bekerja dengan JavaScript selama beberapa hari sekarang dan sampai pada titik di mana saya ingin membebani operator untuk objek yang saya tentukan.

Setelah menjalankan tugas di google mencari ini, tampaknya Anda tidak bisa secara resmi melakukan ini, namun ada beberapa orang di luar sana yang mengklaim cara bertele-tele untuk melakukan tindakan ini.

Pada dasarnya saya telah membuat kelas Vector2 dan ingin dapat melakukan hal berikut:

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.

Sebaliknya saya harus melakukan ini:

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. 

Apakah ada pendekatan yang dapat saya lakukan untuk membebani operator di kelas Vector2 saya? Karena ini hanya terlihat jelek.

Lee Brindley
sumber
2
kemungkinan duplikasi Overloading Arithmetic Operator di JavaScript?
Pointy
1
Baru saja menemukan perpustakaan yang membebani operator. Belum mencobanya dan tidak tahu seberapa baik kerjanya: google.com/…
fishinear

Jawaban:

105

Seperti yang Anda temukan, JavaScript tidak mendukung kelebihan beban operator. Yang paling dekat yang bisa Anda lakukan adalah mengimplementasikan toString(yang akan dipanggil saat instance perlu dipaksa menjadi string) dan valueOf(yang akan dipanggil untuk memaksanya menjadi angka, misalnya saat menggunakan +untuk penambahan, atau dalam banyak kasus saat menggunakannya untuk penggabungan karena +mencoba melakukan penambahan sebelum penggabungan), yang sangat terbatas. Keduanya tidak memungkinkan Anda membuat Vector2objek sebagai hasilnya.


Untuk orang yang datang ke pertanyaan ini yang menginginkan string atau angka sebagai hasil (bukan a Vector2), berikut adalah contoh dari valueOfdan toString. Contoh-contoh ini tidak menunjukkan kelebihan beban operator, hanya memanfaatkan penanganan bawaan JavaScript yang dikonversi ke primitif:

valueOf

Contoh ini menggandakan nilai valproperti objek sebagai respons untuk dipaksa menjadi primitif, misalnya melalui +:

Atau dengan ES2015 class:

Atau hanya dengan objek, tanpa konstruktor:

toString

Contoh ini mengonversi nilai valproperti objek menjadi huruf besar sebagai respons untuk dipaksa menjadi primitif, misalnya melalui +:

Atau dengan ES2015 class:

Atau hanya dengan objek, tanpa konstruktor:

TJ Crowder
sumber
1
Meskipun tidak didukung dalam JS, cukup umum akhir-akhir ini untuk memperluas JS dengan fitur khusus dan transparan kembali ke JS biasa, misalnya, SweetJS bertujuan untuk mengatasi masalah ini dengan tepat.
Dmitri Zaitsev
1
Apakah operator perbandingan di Datekelas secara implisit mengonversi tanggal menjadi angka menggunakan valueOf? Misalnya bisa Anda lakukan date2 > date1dan akan benar jika date2dibuat setelahnya date1.
Sean Letendre
1
@SeanLetendre: Ya. >, <, >=, Dan <=(tapi tidak ==, ===, !=, atau !==) menggunakan Abstrak Relational Perbandingan operasi, yang menggunakan ToPrimitivedengan "nomor" petunjuk. Pada sebuah Dateobjek, yang menghasilkan angka yang getTimekembali (nilai milidetik-sejak-The-Epoch).
TJ Crowder
24

Seperti yang dikatakan TJ, Anda tidak dapat membebani operator di JavaScript. Namun Anda dapat memanfaatkan valueOffungsi untuk menulis peretasan yang terlihat lebih baik daripada menggunakan fungsi seperti addsetiap saat, tetapi memberikan batasan pada vektor bahwa x dan y berada di antara 0 dan MAX_VALUE. Ini kodenya:

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

Kemudian Anda bisa menulis persamaan seperti ini:

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]
pengguna2259659
sumber
7
Anda pada dasarnya baru saja menulis kode untuk metode OP add... Sesuatu yang tidak ingin mereka lakukan.
Ian Brindley
16
@IanBrindley OP ingin membebani operator, yang secara jelas menyiratkan bahwa dia berencana untuk menulis fungsi seperti itu. Perhatian OP adalah harus memanggil "tambah", yang tidak wajar; secara matematis, kami merepresentasikan penjumlahan vektor dengan sebuah +tanda. Ini adalah jawaban yang sangat bagus yang menunjukkan bagaimana menghindari pemanggilan nama fungsi yang tidak wajar untuk objek kuasi-numerik.
Kittsil
1
@Kittsil Pertanyaannya menunjukkan bahwa saya sudah menggunakan fungsi add. Meskipun fungsi di atas bukanlah fungsi yang buruk sama sekali tidak menjawab pertanyaan, jadi saya setuju dengan Ian.
Lee Brindley
Sampai sekarang ini adalah satu-satunya cara yang mungkin. Satu-satunya fleksibilitas yang kami miliki dengan +operator adalah kemampuan mengembalikan Numbersebagai pengganti salah satu operan. Oleh karena itu, setiap fungsionalitas penambahan yang berfungsi sebagai Objectcontoh harus selalu menyandikan objek sebagai Number, dan akhirnya mendekodekannya.
Gershom
Perhatikan bahwa ini akan mengembalikan hasil yang tidak diharapkan (bukannya memberikan kesalahan) saat mengalikan dua vektor. Juga koordinatnya harus bilangan bulat.
pengguna202729
8

FYI paper.js memecahkan masalah ini dengan membuat PaperScript, javascript cakupan dan mandiri dengan operator overloading vektor, yang kemudian diproses kembali menjadi javascript.

Tetapi file skrip makalah perlu secara khusus ditentukan dan diproses seperti itu.

Joshua Penman
sumber
8

Sebenarnya, ada satu varian dari JavaScript yang tidak operator overloading dukungan. ExtendScript, bahasa skrip yang digunakan oleh aplikasi Adobe seperti Photoshop dan Illustrator, memang memiliki kelebihan beban operator. Di dalamnya, Anda bisa menulis:

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;

Ini dijelaskan secara lebih rinci dalam "Panduan alat JavaScript Adobe Extendscript" ( tautan terkini di sini ). Sintaksnya tampaknya didasarkan pada draf standar ECMAScript (sekarang sudah lama ditinggalkan).

J. Peterson
sumber
11
ExtendScript! = JavaScript
Andrio Skur
3
Mengapa jawaban ExtendScript di-downvot sementara jawaban PaperScript di-upvoted? IMHO jawaban ini juga bagus.
xmedeko
5

Dimungkinkan untuk melakukan matematika vektor dengan dua angka yang dikemas menjadi satu. Izinkan saya menunjukkan contoh terlebih dahulu sebelum saya menjelaskan cara kerjanya:

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

Saya menggunakan fakta bahwa jika Anda menggeser dua angka X kali dan kemudian menambahkan atau menguranginya sebelum menggesernya kembali, Anda akan mendapatkan hasil yang sama seolah-olah Anda tidak menggesernya untuk memulai. Demikian pula perkalian dan pembagian skalar bekerja secara simetris untuk nilai-nilai yang bergeser.

Nomor JavaScript memiliki presisi integer 52 bit (64 bit float), jadi saya akan mengemas satu angka menjadi 26 bit yang tersedia lebih tinggi, dan satu ke lebih rendah. Kode dibuat sedikit lebih berantakan karena saya ingin mendukung nomor yang ditandatangani.

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

Satu-satunya downside yang dapat saya lihat dengan ini adalah bahwa x dan y harus berada dalam kisaran + -33 juta, karena masing-masing harus muat dalam 26 bit.

Stuffe
sumber
Dimanakah definisi vec_pack?
Menjijikkan
1
@ Menjijikkan Hmm maaf, sepertinya saya lupa menambahkan itu ... Itu sekarang sudah diperbaiki :)
Stuffe
4

Meskipun bukan jawaban pasti untuk pertanyaan tersebut, dimungkinkan untuk menerapkan beberapa metode python __magic__ menggunakan Simbol ES6

Sebuah [Symbol.toPrimitive]()metode tidak membiarkan Anda menyiratkan panggilan Vector.add(), tetapi akan membiarkan Anda menggunakan sintaks seperti 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;
        }
    }
}
James McGuigan
sumber
3

Kita dapat menggunakan React-like Hooks untuk mengevaluasi fungsi panah dengan nilai yang berbeda dari valueOfmetode pada setiap iterasi.

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 menggunakan ide yang sama untuk Vector3.

FTOH
sumber
2

Menarik juga eksperimental operator perpustakaan -overloading-js . Itu melakukan overloading dalam konteks yang ditentukan (fungsi panggilan balik) saja.

xmedeko.dll
sumber
1

Saya menulis perpustakaan yang mengeksploitasi banyak peretasan jahat untuk melakukannya dalam JS mentah. Ini memungkinkan ekspresi seperti ini.

  • Bilangan kompleks:

    >> Complex()({r: 2, i: 0} / {r: 1, i: 1} + {r: -3, i: 2}))

    <- {r: -2, i: 1}

  • Diferensiasi otomatis:

    Biarkan f(x) = x^3 - 5x:

    >> var f = x => Dual()(x * x * x - {x:5, dx:0} * x);

    Sekarang petakan pada beberapa nilai:

    >> [-2,-1,0,1,2].map(a=>({x:a,dx:1})).map(f).map(a=>a.dx)

    <- [ 7, -2, -5, -2, 7 ]

    yaitu f'(x) = 3x^2 - 5.

  • Polinomial:

    >> Poly()([1,-2,3,-4]*[5,-6]).map((c,p)=>''+c+'x^'+p).join(' + ')

    <- "5x^0 + -16x^1 + 27x^2 + -38x^3 + 24x^4"

Untuk masalah khusus Anda, Anda akan menentukan Vector2fungsi (atau mungkin sesuatu yang lebih pendek) menggunakan pustaka, lalu tulisx = Vector2()(x + y);

https://gist.github.com/pyrocto/5a068100abd5ff6dfbe69a73bbc510d7

Mike Tetap
sumber