In-depth look at function() {} + function() {} operation in JavaScript

Chidume Nnamdi 🔥💻🎵🎮
Dev Proto
Published in
6 min readJul 30, 2020

Before we proceed can you guess the answer? :)

Yeah, I know, it’s odd. How can you ever want to add functions? But JS can be funny :), you won’t believe what it will give us.

function() {} + function() {}

Output:

function() {}function() {}

😂

Conglomeration😁😂… or is it concatenation of the function’s source. 😂

So, adding or evaluating a function would yield the function’s source. That’s one tip for the toolbelt.

In this post, we will look at the EcmaSpec to see how that happens.

“Sum those functions for me, will ya?”

function() {} + function() {}

We will start by looking at the EcmaSpec for the Addition operator

Addition operator either does number addition or string concatenation. If the left value or right is a string, it will perform string concatenation, else number addition will be applied.

Following this spec with our two functions addition.

The LHS function() {} will be evaluated to return a function object Function. The function object will be held in lref. The function object is converted to its primitive value by calling ToPrimitive and passing it as an argument.

Let’s see the spec on ToPrimitive:

ToPrimitive requires a hint to know how to convert the argument. Looking above, we can deduce it deals with two hints: number and string. If there is no hint present it sets the hint to default, if the hint is String it sets hint to String, if the hint is Number it sets the hint to Number.

In our case, The addition operator passed no hint type, so our function object will take hint default.

Next, the @@toPrimitive is gotten from our function object.

This @@toPrimitive is a way JS allows us to add our own implementation on how our objects will be converted to primitive.

So here, it is checked if its present, if present the method is called and the result is returned without using the JS default to primitive conversion method.

Function object doesn’t implement their own @@toPrimitive, so we move to f., since our hint is default, here it is set to string.

Then OrdinaryToPrimitive is called with our function object and hint string.

Let’s see the spec for OrdinaryToPrimitive

It begins by asserting that the argument is an Object, we will pass because it was called with a function object, then it asserts that the hint is a string and the value is either string or number, we will pass because ours is a string.

It sets an array with method names to be called on the object O. if the hint is string the methodNames array has toString and valueOf if not, valueOf comes before toString.

This valueOf and toString are methods to be called on the object O. In our case the methods will be called on the function object.

These two methods are ways in which we set what is to be returned when our object is to be converted to either string or number.

If we have an object like this:

let obj = {
prop: 89
}

We can the numeric representation of the object by defining valueOf method in it:

let obj = {
prop: 89,
valueOf: function() {
return 89
},
toString:function() {
return "89"
}
}

When it comes to coercing obj to number the valueOf will be called which we see will return 89, a number.

obj + 1 // 90

Coercing the obj to string will have its toString method called which will return “89”

obj + "1" // 891

We now see the reason why OrdtinaryToPrimitive calls valueOf and toString on objects. Operators majorly perform arithmetic operations and arithmetic operations are done on numbers and strings. There is no way you can add an object, or subtract objects or apply any arithmetic operation on JS data types apart from number and strings(only addition though). That’s why they must be converted to their number representation before doing the math.

Back to the OrdinaryToPrimitive spec:

OrdinaryToPrimitive loops through the methodNames, as we are string, toString is called first on our function object. The method Function.prototype.toString will be called.

Let’s look at the spec Function.prototype.toString():

There are two checks here. One, if the function is an exotic object, it means that the function object is user-defined, the String source code of the function object should be returned, and Two, if the function is a built-in function, the String source code of the function should be returned.

What this tells us is that the toString of the Function object returns the source code of the function object.

So for our function object, the source code is this:

function (){}

It’s string representation "function (){}" will be returned.

That will be returned to OrdinaryToPrimitive 5i:

The string source code of our function object will be held in result. 5ii will see that the type is not an Object but a primitive string, so it will be returned.

Then going up back to ToPrimitive spec, here:

The string source code of our function object will be returned g.

Going back up to the Addition spec:

a will have the string source code "function (){}" of the LHS function object. The same will be done for the function object at the RHS, it will also return its string source code "function (){}"

lprim and rprim will both hold the string source “function (){}”

The string test of 7 will pass because both are strings, then, they are tried to be converted to string by passing them to ToString

ToString spec:

As they are already strings, they are simply returned by the ToString method.

Then, they are concatenated.

lstr holding: “function (){}” will be concatenated or joined to rstr which holds “function (){}”.

The result will be:

"function (){}function (){}"

The result will then be returned. :)

We now see why:

>> function (){} + function (){}
// gives
function (){}function (){}

We did for user-defined functions, what happens when we add built-in functions?

For example, adding built-in functions like parseInt, parseFloat, eval, etc have their functions in the native language the JS engine is built on. V8 will have them in C++ implementation. Rhino will have them in Java.

So, it depends on the JS engine to know how to get the native string source code.

V8 returns a string with the name of the function and “[native code]” in its body.

>> parseInt + function() {}
function parseInt() { }
function parseInt() { [native code] }function() {}

The source code of where V8 does this is here:

See, the function JSFunction::ToString, implementation of the Function.prototype.toString in V8. It has checks for printing native function source strings and user-defined function source strings.

See the NativeCodeFunctionSourceString function, it builds the source of built-in functions. See, it appends the “function” string, then builds the name of the function, next it attaches “() { [native code] }”, that’s why we got “function parseInt() { [native code] }” for parseInt.

Conclusion

JS is no magic.

Everything you need to know is in the spec and in the JS engines sources.

Thanks for stopping by my little corner of the web. I think you’ll love my email newsletter about programming advice, tutoring, tech, programming and software development. Just sign up below:

Follow me on Twitter.

--

--

Chidume Nnamdi 🔥💻🎵🎮
Dev Proto

JS | Blockchain dev | Author of “Understanding JavaScript” and “Array Methods in JavaScript” - https://app.gumroad.com/chidumennamdi 📕