Letting operators call methods

One version of real operator overloading works like this: Given the expression

obj1 + obj2

The plus operator is applied to two objects. This triggers the method call

obj1.operator+(obj2)

The closest thing you can achieve in JavaScript is fake operator overloading – triggering two method calls:

obj1.valueOf()
obj2.valueOf()

Those are made because + only works with primitive values and thus needs to convert obj1 and obj2 to primitives. It does so by invoking their valueOf() method. Fake operator overloading is much less useful than real operator overloading: You don’t get access to both operands at the same time and you can’t influence the value returned by +. We’ll later look at tricks that work around these limitations.

What operators can be used for fake operator overloading? All operators that coerce (convert) their operands to primitives. The following two objects allow you to test which ones do:

Equality operators, inequality operators, and boolean operators can work with objects and thus don't coerce. For example:

> obj1 === obj2
false
> obj1 && obj2
{ valueOf: [Function] }

Triggering calls. If its operands are produced by function calls, then a binary operator triggers a total of four function (or method) calls: First, it evaluates the two operand expressions to values (in this case, objects). Then, it converts two objects to primitives. The following function allows us to examine what happens and in which order:

Hence: First, the left operand is evaluated, then the right operand. Next, the left value is converted to a primitive, then the right value. It is slightly perplexing to see that valueOf_LEFT does not happen directly after func_LEFT, but it makes sense if you think of function calls, where one also first evaluates the parameters before executing the function body. Some operators even convert their right operands first:

Explanation: The << operator calls StringBuilder.prototype.valueOf which marks the current instance as the “receiver” of subsequent “messages” (by storing it in StringBuilder.current). The messages are sent via add(), which wraps its argument value in an object. When that object is contacted by the operator, it adds value to the current receiver.

def.js – fake operator overloading used for an inheritance API

Tobias Schneider’s def.js is where I first saw the idea of fake operator overloading. def.js uses it to implement a small domain-specific language, giving you a rubyesque syntax for inheritance in JavaScript. Example:

Therefore, whatever is returned by def() must be both callable (i.e., a function) and a potential fake operand. Furthermore, there are two ways of invoking Person (which has been created by def.js):

As a constructor: Then it simply produces a new instance.

As a function: Then it must return an object that works as a fake operand.

Expression (II) is evaluated in three steps (simplified for the purpose of this explanation):

Perform the function call def("Ninja") which creates a new constructor Ninja and returns a function D. D could be called, like in (I). In which case it would add properties to Ninja.prototype. But here it is used as a fake operand – def() has added the method D.valueOf() which will be invoked in step 3. D is also stored in a global variable (hidden inside a closure).

Person ({...}) is invoked as a function and stores two values inside D:

D.props holds Person’s argument

D.super refers to Person

The operator calls D.valueOf() which first lets Ninja (which is still accessible via D’s closure) inherit from D.super and then calls D to add the properties stored in D.props.

Triggering even more calls

func() above allowed us to get four function (and method) calls out of the following expression:

The trick here is that << internally performs a ToNumber() [1] conversion. ToNumber() first tries valueOf(). If that method does not return a primitive value, it continues with toString(). If toString() does not return a primitive, either, then a TypeError is thrown. Note that ToNumber() only expects the result of either valueOf() or toString() to be a primitive which it then converts to a number. Hence, toString() returning a string is only enforced by convention in JavaScript.

Detecting the operator

If you combine fake operator overloading with a setter, you can detect which operator is used [credit: inspired by an idea of Tobias Schneider’s]:

This method stores the operand we actually want to use away in a global variable. It then returns 3 (a value that the plus operator can work with). That number is the lowest natural number x for which all of the following expressions produce different results:

x + x
x - x
x * x
x / x

p._ has a setter that receives the result of 3 being added, multiplied etc., figures out which operator was used and processes Point.operands accordingly.

How useful is fake operator overloading?

While fake operator overloading is a fun hack, you probably shouldn’t use it in production code: you cannot freely use the result computed by an operator and need side effects (such as storing values in global variables) to access all operands at the same time. Furthermore, many operands need to be wrapped in an object with a valueOf() method in order for this scheme to work. For example, we couldn’t use strings directly with StringBuilder, we had to convert them via add(). But at the very least, fake operator overloading allows you to impress your friends by unusual-looking code.