Javascript: nạp chồng toán tử


93

Tôi đã làm việc với JavaScript trong vài ngày nay và đã đến lúc tôi muốn nạp chồng các toán tử cho các đối tượng đã xác định của mình.

Sau một thời gian trên google tìm kiếm điều này, có vẻ như bạn không thể chính thức làm điều này, nhưng có một vài người ngoài kia tuyên bố một số cách dài dòng để thực hiện hành động này.

Về cơ bản, tôi đã tạo một lớp Vector2 và muốn có thể thực hiện những việc sau:

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.

Thay vào đó, tôi phải làm điều này:

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ó cách nào tôi có thể thực hiện để xử lý quá tải các toán tử trong lớp Vector2 của mình không? Vì điều này chỉ trông đơn giản là xấu xí.



1
Chỉ gặp một thư viện quá tải toán tử. Đã không thử nó và không biết như thế nào nó hoạt động, mặc dù: google.com/...
fishinear

Câu trả lời:


102

Như bạn đã thấy, JavaScript không hỗ trợ nạp chồng toán tử. Cách gần nhất mà bạn có thể đến là thực hiện toString(sẽ được gọi khi cá thể cần được ép buộc trở thành một chuỗi) và valueOf(sẽ được gọi để ép nó thành một số, chẳng hạn như khi sử dụng +cho phép cộng hoặc trong nhiều trường hợp khi sử dụng nó để nối vì +cố gắng thực hiện phép cộng trước khi nối), điều này khá hạn chế. Vector2Kết quả là không cho phép bạn tạo một đối tượng.


Tuy nhiên, đối với những người đến với câu hỏi này, những người muốn kết quả là một chuỗi hoặc số (thay vì a Vector2), đây là các ví dụ về valueOftoString. Những ví dụ này không chứng minh việc nạp chồng toán tử, chỉ tận dụng khả năng xử lý tích hợp của JavaScript để chuyển đổi thành nguyên thủy:

valueOf

Ví dụ này tăng gấp đôi giá trị của thuộc tính của một đối tượng valđể đáp ứng với việc bị ép buộc về nguyên thủy, ví dụ +: thông qua :

Hoặc với ES2015's class:

Hoặc chỉ với các đối tượng, không có hàm tạo:

toString

Ví dụ này chuyển đổi giá trị thuộc tính của một đối tượng valthành chữ hoa để phản ứng với việc bị ép buộc về nguyên thủy, ví dụ +:

Hoặc với ES2015's class:

Hoặc chỉ với các đối tượng, không có hàm tạo:


1
Mặc dù nó không được hỗ trợ trong JS thích hợp, nhưng ngày nay việc mở rộng JS với các tính năng tùy chỉnh và chuyển tải trở lại JS thuần túy là khá phổ biến, chẳng hạn như SweetJS đang hướng tới giải quyết chính xác vấn đề này.
Dmitri Zaitsev

1
Các toán tử so sánh trên Datelớp có chuyển đổi ngầm định ngày thành số bằng cách sử dụng valueOfkhông? Ví dụ bạn có thể làm date2 > date1và nó sẽ đúng nếu date2được tạo sau đó date1.
Sean Letendre

1
@SeanLetendre: Có. >, <, >=, Và <=(nhưng không phải ==, ===, !=, hoặc !==) sử dụng Abstract Relational So sánh hoạt động, trong đó sử dụng ToPrimitivevới gợi ý "number". Trên một Dateđối tượng, kết quả là số getTimetrả về (giá trị mili giây-kể từ kỷ nguyên).
TJ Crowder

23

Như TJ đã nói, bạn không thể nạp chồng các toán tử trong JavaScript. Tuy nhiên, bạn có thể tận dụng lợi thế củavalueOf hàm để viết một bản hack trông đẹp hơn so với việc sử dụng các hàm như addmọi lần, nhưng áp đặt các ràng buộc đối với vectơ mà x và y nằm trong khoảng từ 0 đến MAX_VALUE. Đây là mã:

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

Sau đó, bạn có thể viết các phương trình như sau:

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
Về cơ bản, bạn vừa viết mã cho addphương thức của OP ... Một cái gì đó mà họ không muốn làm.
Ian Brindley

15
@IanBrindley OP muốn nạp chồng một toán tử, điều này ngụ ý rõ ràng rằng anh ta dự định viết một hàm như vậy. Mối quan tâm của OP là phải gọi "add", điều này không tự nhiên; về mặt toán học, chúng ta biểu diễn phép cộng vectơ với một +dấu. Đây là một câu trả lời rất hay cho thấy cách tránh gọi một tên hàm không tự nhiên cho các đối tượng gần như số.
Kittsil

1
@Kittsil Câu hỏi cho thấy rằng tôi đang sử dụng một chức năng thêm. Mặc dù hàm trên không phải là một hàm tồi nhưng nó không giải quyết được câu hỏi, vì vậy tôi đồng ý với Ian.
Lee Brindley

Cho đến nay, đây là cách duy nhất có thể. Sự linh hoạt duy nhất mà chúng ta có với +toán tử là khả năng trả về Numbermột thay thế cho một trong các toán hạng. Do đó, bất kỳ chức năng bổ sung nào hoạt động các Objectthể hiện phải luôn mã hóa đối tượng dưới dạng một Number, và cuối cùng giải mã nó.
Gershom

Lưu ý rằng điều này sẽ trả về một kết quả không mong muốn (thay vì đưa ra lỗi) khi nhân hai vectơ. Ngoài ra, tọa độ phải là số nguyên.
user202729

8

FYI paper.js giải quyết vấn đề này bằng cách tạo PaperScript, một javascript có phạm vi khép kín với toán tử nạp chồng các vectơ, sau đó nó sẽ xử lý lại thành javascript.

Nhưng các tệp giấy tờ cần phải được chỉ định cụ thể và xử lý như vậy.


6

Trên thực tế, có một biến thể của mã JavaScript mà không điều hành hỗ trợ quá tải. ExtendScript, ngôn ngữ kịch bản được sử dụng bởi các ứng dụng Adobe như Photoshop và Illustrator, có tính năng nạp chồng toán tử. Trong đó, bạn có thể viết:

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;

Điều này được mô tả chi tiết hơn trong "Hướng dẫn công cụ JavaScript Adobe Extendscript" ( liên kết hiện tại ở đây ). Cú pháp rõ ràng là dựa trên bản nháp (hiện đã bị bỏ qua từ lâu) của tiêu chuẩn ECMAScript.


9
ExtendScript! = JavaScript
Andrio Skur,

1
Tại sao câu trả lời ExtendScript bị phản đối trong khi câu trả lời PaperScript được ủng hộ? IMHO câu trả lời này cũng tốt.
xmedeko

4

Có thể làm toán vectơ với hai số được đóng gói thành một. Đầu tiên hãy để tôi đưa ra một ví dụ trước khi tôi giải thích cách nó hoạt động:

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

Tôi đang sử dụng thực tế là nếu bạn chuyển hai số X lần và sau đó cộng hoặc trừ chúng trước khi chuyển chúng trở lại, bạn sẽ nhận được kết quả giống như khi bạn chưa chuyển chúng từ đầu. Tương tự, phép nhân và phép chia vô hướng hoạt động đối xứng với các giá trị được dịch chuyển.

Một số JavaScript có độ chính xác số nguyên là 52 bit (số nổi 64 bit), vì vậy tôi sẽ đóng gói một số thành 26 bit có sẵn cao hơn và một số vào số thấp hơn. Mã được làm lộn xộn hơn một chút vì tôi muốn hỗ trợ các số có chữ ký.

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

Nhược điểm duy nhất tôi có thể thấy với điều này là x và y phải nằm trong phạm vi + -33 triệu, vì chúng phải vừa với nhau trong phạm vi 26 bit.


Định nghĩa của vec_pack ở đâu?
tởm

1
@Disgusting Hmm xin lỗi, có vẻ như tôi đã quên thêm điều đó ... Điều đó hiện đã được sửa :)
Stuffe

3

Mặc dù không phải là câu trả lời chính xác cho câu hỏi, nhưng có thể triển khai một số phương thức python __magic__ bằng cách sử dụng Ký hiệu ES6

Một [Symbol.toPrimitive]()phương thức không cho phép bạn ngụ ý một cuộc gọi Vector.add(), nhưng sẽ cho phép bạn sử dụng cú pháp chẳng hạn như 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

Chúng ta có thể sử dụng React-like Hooks để đánh giá hàm mũi tên với các giá trị khác nhau từ valueOfphương thức trên mỗi lần lặp.

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

Thư viện @ js-basics / vector sử dụng cùng một ý tưởng cho Vector3.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.