4

Okay so I wrote this simple javascript function to make my code more readable:

Number.prototype.isBetween=function(a,b){
    return(this>=a&&this<b);
};

Now this turns out to be VERY slow: I tried this "benchmark" (I don't really know how to properly do this stuff but this proves my point either way):

var res=0;
var k=20;
var t=new Date().getTime();
for(var i=0;i<10000000;i++){if(k.isBetween(13,31)){res++;}}
console.log(new Date().getTime()-t);

versus

var res=0;
var k=20;
var t=new Date().getTime();
for(var i=0;i<10000000;i++){if(k>=13&&k<31)){res++;}}
console.log(new Date().getTime()-t);

and the first script takes about 3000ms in chrome (and chrome is what I'm using and what I'm interested in), whereas the second script takes only 24ms - a whole factor 125 faster. Is extending the existing classes javascript provides just a really bad idea? What is going on here?

vrugtehagel
  • 1,009
  • 1
  • 9
  • 20
  • Extending existing classes may not be a bad idea (see jQuery plugins) but extending **standard built-in classes** is a bad idea because future versions of javascript may break your code. Read about what happened to prototype.js – slebetman Feb 04 '17 at 12:20
  • Constant propagation. The term you're looking for is [*constant propagation*](https://en.wikipedia.org/wiki/Constant_folding). In your second script, there's not only no function call, there's likely not even a computation happing at all. – Bergi Feb 04 '17 at 13:09
  • Possible duplicate of [Extending String.prototype performance shows that function calls are 10x faster](https://stackoverflow.com/q/38407760/1048572) – Bergi Mar 02 '22 at 00:22

1 Answers1

8

The reason is that for the method isBetween to be applied, the subject k needs to be converted to a Number object. This is called boxing or primitive wrapping.

Then the isBetween method is applied where the comparison operator > will need to retrieve the primitive value from the this object, ... twice.

This is all additional overhead that comes on top of the additional function call (involving the stack).

This total overhead is more work than the actual comparison that needs to happen, and so the performance impact is relatively huge.

Strict mode

As @Bergi mentioned in the comments below, the above-described wrapping does not happen in strict mode MDN :

the value passed as this to a function in strict mode is not forced into being an object (a.k.a. "boxed"). [...] automatic boxing [is] a performance cost [...] Thus for a strict mode function, the specified this is not boxed into an object.

You'll find the added performance cost will evaporate when switching to strict mode with:

"use strict";
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Why does k need to be converted to a number? Is it not already a number by setting `k=20`? – vrugtehagel Feb 04 '17 at 12:20
  • typeof k is still "number". It's need to be converted to object. – Sanchay Kumar Feb 04 '17 at 12:21
  • There is a difference between a primitive number, and a Number object. A primitive value has no methods, and so it will be converted to an object on the fly. – trincot Feb 04 '17 at 12:21
  • Also the reason for second to be faster is here you are doing all calculation in primitive data type by just using numbers and operators. – Sanchay Kumar Feb 04 '17 at 12:25
  • It looks like it's almost all due to the object conversion. I made a jsperf https://jsperf.com/number-method-vs-function and a regular function is almost identical performance to the inline comparison, but the method is 200x slower. – Barmar Feb 04 '17 at 12:27
  • Just `"use strict";`, then there won't be any conversion going on and the prototype method will be as fast as the native ones – Bergi Feb 04 '17 at 13:06