132

I've been working with JavaScript for a few days now and have got to a point where I want to overload operators for my defined objects.

After a stint on google searching for this it seems you can't officially do this, yet there are a few people out there claiming some long-winded way of performing this action.

Basically I've made a Vector2 class and want to be able to do the following:

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.

Instead I'm having to do this:

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. 

Is there an approach I can take to overload operators in my Vector2 class? As this just looks plain ugly.

Dan Mandel
  • 637
  • 1
  • 8
  • 26
Lee Brindley
  • 6,242
  • 5
  • 41
  • 62
  • 3
    possible duplicate of [Overloading Arithmetic Operators in JavaScript?](http://stackoverflow.com/questions/1634341/overloading-arithmetic-operators-in-javascript) – Pointy Oct 27 '13 at 16:54
  • 2
    Just came across an operator overloading library. Haven't tried it and don't know how well it works, though: https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&cad=rja&uact=8&ved=0ahUKEwjDtP7GnN_YAhVCdCwKHTuWAwYQFgg0MAE&url=https%3A%2F%2Fgithub.com%2Fkushal-likhi%2Foperator-overloading-js&usg=AOvVaw3INPwlF7ksxvBfjxphJiMM – fishinear Jan 17 '18 at 15:00

10 Answers10

155

As you've found, JavaScript doesn't support operator overloading. The closest you can come is to implement toString (which will get called when the instance needs to be coerced to being a string) and valueOf (which will get called to coerce it to a number, for instance when using + for addition, or in many cases when using it for concatenation because + tries to do addition before concatenation), which is pretty limited. Neither lets you create a Vector2 object as a result. Similarly, Proxy (added in ES2015) lets you intercept various object operations (including property access), but again won't let you control the result of += on Vector instances.


For people coming to this question who want a string or number as a result (instead of a Vector2), though, here are examples of valueOf and toString. These examples do not demonstrate operator overloading, just taking advantage of JavaScript's built-in handling converting to primitives:

valueOf

This example doubles the value of an object's val property in response to being coerced to a primitive, for instance via +:

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)

Or with ES2015's class:

class Thing {
    constructor(val) {
      this.val = val;
    }
    valueOf() {
      return this.val * 2;
    }
}

const a = new Thing(1);
const b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)

Or just with objects, no constructors:

var thingPrototype = {
    valueOf: function() {
      return this.val * 2;
    }
};

var a = Object.create(thingPrototype);
a.val = 1;
var b = Object.create(thingPrototype);
b.val = 2;
console.log(a + b); // 6 (1 * 2 + 2 * 2)

toString

This example converts the value of an object's val property to upper case in response to being coerced to a primitive, for instance via +:

function Thing(val) {
    this.val = val;
}
Thing.prototype.toString = function() {
    return this.val.toUpperCase();
};

var a = new Thing("a");
var b = new Thing("b");
console.log(a + b); // AB

Or with ES2015's class:

class Thing {
    constructor(val) {
      this.val = val;
    }
    toString() {
      return this.val.toUpperCase();
    }
}

const a = new Thing("a");
const b = new Thing("b");
console.log(a + b); // AB

Or just with objects, no constructors:

var thingPrototype = {
    toString: function() {
      return this.val.toUpperCase();
    }
};

var a = Object.create(thingPrototype);
a.val = "a";
var b = Object.create(thingPrototype);
b.val = "b";
console.log(a + b); // AB
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 3
    While it is not supported within JS proper, it is quite common these days to extend JS with custom features and transpile back to plain JS, for instance, [SweetJS](http://sweetjs.org/) is aiming at addressing exactly this problem. – Dmitri Zaitsev Dec 27 '16 at 02:34
  • 1
    Do the comparison operators on the `Date` class implicitly convert dates to numbers using `valueOf`? For example you can do `date2 > date1` and it will be true if `date2` was created after `date1`. – Sean Letendre Aug 07 '17 at 23:27
  • 1
    @SeanLetendre: Yes. `>`, `<`, `>=`, and `<=` (but not `==`, `===`, `!=`, or `!==`) use the [Abstract Relational Comparison](http://www.ecma-international.org/ecma-262/8.0/index.html#sec-abstract-relational-comparison) operation, which uses [`ToPrimitive`](http://www.ecma-international.org/ecma-262/8.0/index.html#sec-toprimitive) with hint "number". On a `Date` object, that results in the number that `getTime` returns (the milliseconds-since-The-Epoch value). – T.J. Crowder Aug 08 '17 at 04:20
  • It's also possible to [overload the `[]` operator](https://stackoverflow.com/a/25658975/975097) using a `Proxy` object. – Anderson Green Jul 04 '21 at 19:13
  • @AndersonGreen - That's not really overloading the *operator* (it also affects `.`, for instance), but yes, you can intercept various object operations (including property access). That doesn't help us with `+=`, but... – T.J. Crowder Jul 04 '21 at 21:16
34

As T.J. said, you cannot overload operators in JavaScript. However you can take advantage of the valueOf function to write a hack which looks better than using functions like add every time, but imposes the constraints on the vector that the x and y are between 0 and MAX_VALUE. Here is the code:

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

Then you can write equations like this:

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]
mrvladimir
  • 519
  • 4
  • 8
  • 10
    You have basically just written the code for the OP's `add` method... Something they didn't want to do. – Ian Brindley Jan 09 '15 at 15:21
  • 33
    @IanBrindley The OP wanted to overload an operator, which clearly implies that he planned to write such a function. OP's concern was with having to call "add," which is unnatural; mathematically, we represent vector addition with a `+` sign. This is a very good answer showing how to avoid calling an unnatural function name for quasi-numeric objects. – Kittsil May 18 '16 at 05:13
  • 3
    @Kittsil The question shows that i'm already using an add function. Although the function above isn't a bad function at all it did not address the question, so i'd agree with Ian. – Lee Brindley Jul 21 '16 at 09:10
  • 1
    As of yet this is the only possible way. The only flexibility we have with the `+` operator is an ability to return a `Number` as a replacement for one of the operands. Therefore any adding functionality which works `Object` instances must always encode the object as a `Number`, and eventually decode it. – Gershom Maes Jan 11 '18 at 22:03
  • 2
    Note that this will return an unexpected result (instead of give an error) when multiplying two vector. Also the coordinates must be integer. – user202729 Jan 22 '20 at 09:12
  • @LeeBrindley, I am surprised to read that you agree with Ian. (1) You asked about overloading, which any way requires a function to define how exactly it should be overloaded. (2) The final function call `V()` is necessary as well, as there is no way to silently coerce the result of an arithmetic expression to an instance of `Vector`. (3) You have accepted an answer that does not do better (nor worse) in answering your question. I agree with what Kittsil wrote (as the community seems to do). – trincot Feb 19 '20 at 12:19
  • This is a decent alternative. But although code looks more "beautiful", performance is significantly reduced as compared to `x = x.add(y);` – Manu Manjunath Aug 30 '21 at 07:21
  • Of course, this only works for integer vectors, which is generally not how they are used. A vector like `[0.5, 0]` would be decoded back into `[0, 500000]`. – oisyn Aug 26 '22 at 09:24
19

It's possible to do vector math with two numbers packed into one. Let me first show an example before I explain how it works:

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

I am using the fact that if you bit shift two numbers X times and then add or subtract them before shifting them back, you will get the same result as if you hadn't shifted them to begin with. Similarly scalar multiplication and division works symmetrically for shifted values.

A JavaScript number has 52 bits of integer precision (64 bit floats), so I will pack one number into he higher available 26 bits, and one into the lower. The code is made a bit more messy because I wanted to support signed numbers.

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

The only downside I can see with this is that the x and y has to be in the range +-33 million, since they have to fit within 26 bits each.

Basic Block
  • 729
  • 9
  • 17
11

Actually, there is one variant of JavaScript that does support operator overloading. ExtendScript, the scripting language used by Adobe applications such as Photoshop and Illustrator, does have operator overloading. In it, you can write:

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;

This is described in more detail in the "Adobe Extendscript JavaScript tools guide" (current link here). The syntax was apparently based on a (now long abandoned) draft of the ECMAScript standard.

J. Peterson
  • 1,996
  • 1
  • 24
  • 21
10

FYI paper.js solves this issue by creating PaperScript, a self-contained, scoped javascript with operator overloading of vectors, which it then processing back into javascript.

But the paperscript files need to be specifically specified and processed as such.

Joshua Penman
  • 415
  • 1
  • 5
  • 13
  • 1
    And this comment answers my question. I was reading paper.js code and wondering how they overloaded JS operators to do object math. Thanks! – Ryan Griggs Jan 27 '22 at 22:09
9

We can use React-like Hooks to evaluate arrow function with different values from valueOf method on each iteration.

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

const Vector2 = (function() {
  let index = -1
  return function(x, y) {
    if (typeof x === 'function') {
      const calc = x
      index = 0, x = calc()
      index = 1, y = calc()
      index = -1
    }
    return Object.assign([x, y], {
      valueOf() {
        return index == -1 ? this.toString() : this[index]
      },
      toString() {
        return `[${this[0]}, ${this[1]}]`
      },
      len() {
        return Math.sqrt(this[0] ** 2 + this[1] ** 2)
      }
    })
  }
})()

const a = Vector2(1, 2)
const b = Vector2(2, 4)

console.log('a = ' + a) // a = [1, 2]
console.log(`b = ${b}`) // b = [2, 4]

const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
a[0] = 12
const d = Vector2(() => (2 * a + b) / 2) // [13, 4]
const normalized = Vector2(() => d / d.len()) // [0.955..., 0.294...]

console.log(c, d, normalized)

Library @js-basics/vector uses the same idea for Vector3.

FTOH
  • 101
  • 2
  • 3
9

I wrote a library that exploits a bunch of evil hacks to do it in raw JS. It allows expressions like these.

  • Complex numbers:

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

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

  • Automatic differentiation:

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

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

    Now map it over some values:

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

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

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

  • Polynomials:

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

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

For your particular problem, you would define a Vector2 function (or maybe something shorter) using the library, then write x = Vector2()(x + y);

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

Mike Stay
  • 1,071
  • 8
  • 17
7

Whilst not an exact answer to the question, it is possible to implement some of the python __magic__ methods using ES6 Symbols

A [Symbol.toPrimitive]() method doesn't let you imply a call Vector.add(), but will let you use syntax such as 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
  • 7,542
  • 4
  • 26
  • 29
5

Interesting is also experimental library operator-overloading-js . It does overloading in a defined context (callback function) only.

xmedeko
  • 7,336
  • 6
  • 55
  • 85
  • 3
    For anyone who's interested how this one works, it parses the string representation of the function and builds a new function at runtime that replaces the operators with function calls. – Mike Stay Mar 31 '21 at 15:30
0

How about something like this?

class Vector2 {
  /**
   * @param {number} x
   * @param {number} y
  */
  constructor(x, y){
    this.x = x;
    this.y = y;
  }

  /** @param {Vector2} vector */
  "+"(vector){
    return new Vector2(this.x + vector.x, this.y + vector.y);
  }

  /** @param {Vector2} vector */
  "=="(vector){
    return this.x === vector.x && this.y === vector.y;
  }

  get "++"(){
    return new Vector2(this.x++, this.y++);
  }

  /** @param {Vector2} vector */
  static "++"(vector){
    return new Vector2(++vector.x, ++vector.y);
  }

  /** @param {Vector2} vector */
  set ""(vector){
    this.x = vector.x;
    this.y = vector.y;
    return this;
  }

};

const vec1 = new Vector2(10, 20);

// get "++"(){}
vec1["++"];

const vec2 = new Vector2(20, 30);
// static "++"(vec1){}
Vector2 ["++"](vec1);

// set ""(vec2){}
vec1[""] = vec2;

const isEqual = (vec1) ["=="] (vec2);

const vec3 = new Vector2(1, 2);
const sumVec = (vec1) ["+"] (vec2) ["+"] (vec3);
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 17 '23 at 18:33