Javascript: ตัวดำเนินการมากเกินไป


95

ฉันทำงานกับ 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

คำตอบ:


105

อย่างที่คุณพบ JavaScript ไม่รองรับการทำงานของตัวดำเนินการมากเกินไป สิ่งที่ใกล้เคียงที่สุดที่คุณสามารถทำได้คือการนำไปใช้toString(ซึ่งจะถูกเรียกเมื่ออินสแตนซ์จำเป็นต้องถูกบีบบังคับให้เป็นสตริง) และvalueOf(ซึ่งจะถูกเรียกให้บังคับให้เป็นตัวเลขตัวอย่างเช่นเมื่อใช้+สำหรับการเพิ่มหรือในหลาย ๆ กรณีเมื่อ ใช้สำหรับการต่อเนื่องจาก+พยายามที่จะเพิ่มก่อนการต่อ) ซึ่งค่อนข้าง จำกัด ไม่อนุญาตให้คุณสร้างVector2วัตถุเป็นผลลัพธ์


สำหรับคนที่จะมาถึงคำถามนี้ที่ต้องการสตริงหรือจำนวนเป็นผล (แทนVector2) แต่นี่เป็นตัวอย่างของและvalueOf toStringตัวอย่างเหล่านี้ไม่ได้แสดงให้เห็นว่าตัวดำเนินการโอเวอร์โหลดเพียงแค่ใช้ประโยชน์จากการจัดการในตัวของ JavaScript ที่แปลงเป็นแบบดั้งเดิม:

valueOf

ตัวอย่างนี้เพิ่มค่าvalคุณสมบัติของอ็อบเจ็กต์เป็นสองเท่าเพื่อตอบสนองต่อการถูกบังคับให้เป็นแบบดั้งเดิมตัวอย่างเช่นผ่าน+:

หรือกับ ES2015 class:

หรือกับวัตถุไม่มีตัวสร้าง:

toString

ตัวอย่างนี้แปลงค่าของvalคุณสมบัติของวัตถุเป็นตัวพิมพ์ใหญ่เพื่อตอบสนองต่อการถูกบังคับให้เป็นแบบดั้งเดิมตัวอย่างเช่นผ่าน+:

หรือกับ ES2015 class:

หรือกับวัตถุไม่มีตัวสร้าง:


1
แม้ว่าจะไม่ได้รับการสนับสนุนภายใน JS ที่เหมาะสม แต่ในปัจจุบันเป็นเรื่องปกติที่จะขยาย JS ด้วยคุณสมบัติที่กำหนดเองและเปลี่ยนกลับเป็น JS ธรรมดาตัวอย่างเช่นSweetJSมุ่งเป้าไปที่การแก้ไขปัญหานี้อย่างแท้จริง
Dmitri Zaitsev

1
ตัวดำเนินการเปรียบเทียบในDateชั้นเรียนแปลงวันที่เป็นตัวเลขโดยปริยายโดยใช้valueOfหรือไม่? ตัวอย่างเช่นคุณสามารถทำได้date2 > date1และจะเป็นจริงหากdate2สร้างขึ้นหลังจากdate1นั้น
Sean Letendre

1
@SeanLetendre: ครับ >, <, >=และ<=( แต่ไม่==, ===, !=หรือ!==) ใช้บทคัดย่อเชิงเปรียบเทียบการดำเนินงานซึ่งใช้ToPrimitiveกับ "จำนวน" แย้ม บนDateอ็อบเจ็กต์ผลลัพธ์เป็นจำนวนที่getTimeส่งกลับ (ค่ามิลลิวินาทีตั้งแต่ The-Epoch)
TJ Crowder

24

ดังที่ 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

16
@IanBrindley OP ต้องการโอเวอร์โหลดตัวดำเนินการซึ่งบอกเป็นนัยอย่างชัดเจนว่าเขาวางแผนที่จะเขียนฟังก์ชันดังกล่าว ความกังวลของ OP คือต้องเรียก "เพิ่ม" ซึ่งผิดธรรมชาติ; ในทางคณิตศาสตร์เราแสดงการบวกเวกเตอร์ด้วย+เครื่องหมาย นี่เป็นคำตอบที่ดีมากที่แสดงวิธีหลีกเลี่ยงการเรียกชื่อฟังก์ชันที่ไม่เป็นธรรมชาติสำหรับวัตถุกึ่งตัวเลข
Kittsil

1
@Kittsil คำถามแสดงว่าฉันใช้ฟังก์ชันเพิ่มอยู่แล้ว แม้ว่าฟังก์ชั่นข้างต้นจะไม่ใช่ฟังก์ชั่นที่ไม่ดี แต่ก็ไม่ได้ตอบคำถาม แต่ฉันก็เห็นด้วยกับเอียน
Lee Brindley

ณ ตอนนี้เป็นวิธีเดียวที่เป็นไปได้ ความยืดหยุ่นเพียงอย่างเดียวที่เรามีกับตัว+ดำเนินการคือความสามารถในการส่งคืนNumbera แทนตัวถูกดำเนินการตัวใดตัวหนึ่ง ดังนั้นฟังก์ชันการเพิ่มใด ๆ ที่ใช้งานObjectอินสแตนซ์ได้จะต้องเข้ารหัสออบเจ็กต์เป็น a Numberเสมอและในที่สุดก็ถอดรหัสมัน
Gershom

โปรดทราบว่าสิ่งนี้จะส่งคืนผลลัพธ์ที่ไม่คาดคิด (แทนที่จะให้ข้อผิดพลาด) เมื่อคูณเวกเตอร์สองตัว พิกัดต้องเป็นจำนวนเต็ม
user202729

8

FYI paper.js แก้ปัญหานี้ได้โดยการสร้าง PaperScript ซึ่งเป็นจาวาสคริปต์ที่มีขอบเขตในตัวพร้อมกับตัวดำเนินการที่โอเวอร์โหลดเวกเตอร์ซึ่งจะประมวลผลกลับเป็นจาวาสคริปต์

แต่ไฟล์ paperscript จำเป็นต้องระบุและประมวลผลโดยเฉพาะ


8

ที่จริงมีเป็นหนึ่งในตัวแปรของ 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 (ขณะนี้ถูกละทิ้งไปนาน)


11
ExtendScript! = JavaScript
Andrio Skur

3
เหตุใดคำตอบของ ExtendScript จึงถูกลงในขณะที่คำตอบของ PaperScript ถูกโหวต IMHO คำตอบนี้ก็ดีเช่นกัน
xmedeko

5

เป็นไปได้ที่จะทำคณิตศาสตร์เวกเตอร์โดยรวมตัวเลขสองตัวไว้ในหนึ่งเดียว ให้ฉันดูตัวอย่างก่อนที่จะอธิบายวิธีการทำงาน:

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

4

แม้ว่าจะไม่ใช่คำตอบที่แน่นอนสำหรับคำถาม แต่ก็เป็นไปได้ที่จะใช้วิธี 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;
        }
    }
}

3

เราสามารถใช้ React-like Hooks เพื่อประเมินฟังก์ชันลูกศรด้วยค่าที่แตกต่างกันจาก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

Library @ js- Basis / vectorใช้แนวคิดเดียวกันสำหรับ Vector3


2

ที่น่าสนใจนอกจากนี้ยังมีการทดลองห้องสมุดประกอบการบรรทุกเกินพิกัด-js มันทำงานมากเกินไปในบริบทที่กำหนด (ฟังก์ชันเรียกกลับ) เท่านั้น


1

ฉันเขียนห้องสมุดที่ใช้ประโยชน์จากการแฮ็กชั่วร้ายจำนวนมากเพื่อทำใน JS ดิบ อนุญาตให้ใช้นิพจน์เช่นนี้

  • จำนวนเชิงซ้อน:

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

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

  • ความแตกต่างอัตโนมัติ:

    ให้f(x) = x^3 - 5x:

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

    ตอนนี้แมปกับค่าบางค่า:

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

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

    กล่าวคือ f'(x) = 3x^2 - 5.

  • พหุนาม:

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

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

สำหรับปัญหาเฉพาะของคุณคุณจะต้องกำหนดVector2ฟังก์ชัน (หรืออาจจะสั้นกว่า) โดยใช้ไลบรารีจากนั้นจึงเขียนx = Vector2()(x + y);

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.