78

I am asking this question because I and my colleague have a dispute on coding style because he prefers arrows function declaration:

const sum = (a, b) => a + b;

And I prefer old-style standalone function declaration:

function sum(a, b) {
    return a + b;
}

My point is that code in old-style more readable and you can more clearly distinguish function and variable declarations. His point is that code with arrow functions just run faster.

Do you know something about actual performance penalties (in v8) when you use old-style standalone function declaration instead of arrow functions? Are that penalties really exists?

Alexander Myshov
  • 2,673
  • 2
  • 19
  • 30
  • 1
    I expect the performance difference will be negligible and vendor dependent. The function body is *run* in the same way. The difference comes in function-object instantiation (eg `prototype` property) and execution context instantiation (e.g. receiver). – Ben Aston May 17 '17 at 16:52
  • @Jonasw My question is more specific. I am interested in v8 specifics only, when other question doesn't specify any environment. I'll edit question to be more clear. – Alexander Myshov May 17 '17 at 17:06
  • @Alexander Myshkov: why dont you run a performance for yourself? Thats the same as we can do... – Jonas Wilms May 17 '17 at 17:07
  • 2
    The new way provided by ECMA6 it's just syntactic sugar for the older way. The body is executed exactly in the same way, so there are no changes in performances between these declarations. Instead, as outlined by @BenAston, there are differences about the instantiation of the function and the context (e.g. with arrow functions `this` doesn't change context). About readability it's also habit. Arrow functions seem more complicated to read, but also because we are used to read them in the older way. Also readability it's a point of view. – quirimmo May 17 '17 at 17:08
  • Hence I said "The difference comes in..." :) – Ben Aston May 17 '17 at 17:10
  • 2
    @Jonasw I don't believe performance tests so much because v8 optimizer can do tricky things. I hope that someone with v8 background can elaborate on this question more deeply. – Alexander Myshov May 17 '17 at 17:11
  • 1
    [Which is faster?](https://ericlippert.com/2012/12/17/performance-rant/) – VLAZ Feb 03 '22 at 16:10

7 Answers7

170

V8 developer here. Arrow functions are (mostly) just "syntactic sugar" for conventional function declarations. There is no performance difference.

Mathias Bynens
  • 137,577
  • 52
  • 212
  • 242
jmrk
  • 26,789
  • 6
  • 42
  • 57
  • 7
    Arrow function expressions are more than syntatic sugar. They basically still create `Function` objects by the way. I never read ES6.0 specs. fully (only ES3.0) so what I say may be not exact, but: calling a `Function` constructed by an arrow function expression causes a different value for `this` in its execution context (which is generally said to be the `this` value from the scope chain of the caller). – Klaider May 17 '17 at 18:28
  • 3
    @Matheus Which means that when the arrow function uses `this`, it'll need to create a closure. Nothing extraordinary. Also I'd expect the creation of the function *object* to be optimised away in the most cases anyway, so there's likely no gain by not having to create a `.prototype` object either. – Bergi May 17 '17 at 18:43
  • 17
    @Matheus: yes, there's a semantic difference around `this`, which is why I wrote "(mostly)". The point of this question (and my answer) is that there's no performance difference -- just like you say, under the hood they're the same `Function` objects, which is why I think it's fair to call them "syntactic sugar". -- @Bergi: Being able to optimize away creation of the function object is the exception, not the rule, unfortunately, because so many things in JavaScript are observable. Closure-heavy programs tend to be quite memory-inefficient for this reason. – jmrk May 18 '17 at 09:59
  • How about the **interpreting time**, not execution time? – Константин Ван Mar 19 '18 at 11:32
  • 4
    I'm not sure what you're asking -- interpreting is one way of executing. Either way, "there's no performance difference" covers it. – jmrk Mar 20 '18 at 05:43
  • one thing I still don't understand is, if arrow function essentially is equal to `func.bind(this)`, then wouldn't `.bind()` causes some slow down and extra memory usage as it has to wrap the original function? – Norman Xu Jun 06 '19 at 09:56
  • 9
    @NormanXu: the implementation of arrow functions doesn't use `.bind()` under the hood, so there is no "original function" to wrap: the arrow function *is* the original. – jmrk Jun 06 '19 at 12:00
  • @jmrk Arrow functions are "syntactic sugar" for function expression not for function declaration. You can verify with online Babel REPL – Sai Kiran Mar 01 '20 at 17:00
  • @jmrk but some form of binding `this` to the arrow function/expression as well as swapping `this` for the bound value upon invocation clearly must take place, even if `.bind` is not used, doesn't it? – zor-el Jun 04 '20 at 09:50
  • 4
    @zor-el: sure, `this` is bound (if it's used), just like other variables from the surrounding scope. No additional swapping is needed after that. Essentially `() => this.foo` is like `var that = this; function() { return that.foo; }`. None of this changes the key point: there's no performance difference between arrow functions and regular functions. – jmrk Jun 04 '20 at 10:07
  • Hey thanks for clarifying, @jrmk. Great insight! Just to recap then, arrow functions are compiled to closures that implicitly close over `this`, and the compiler replaces all `this` references by references to the closed `this` from the surrounding scope. I would suggest to incorporate your above explanation into your answer. As it stands, "syntactic sugar for conventional function declarations" can be a bit misleading, whereas your reply clarifies things perfectly. – zor-el Jun 04 '20 at 12:47
  • @jmrk - Thanks for this. I'm curious if there are indirect improvements (if you have a large number of functions) as a result of not having all of those objects created for the `prototype` property of a traditional function vs. an arrow function? Or are they so small it Just Doesn't Matter™ in the sorts of environments V8 is used in. :-) (Or maybe creating them deferred until/unless they're referenced? Hmmm, I should be able to test that latter question using devtools...) – T.J. Crowder May 31 '22 at 09:59
  • 1
    @T.J.Crowder: your last guess is correct: prototype objects are created lazily when needed, so most functions won't have one, and arrow functions don't have an advantage in this regard. – jmrk May 31 '22 at 10:57
5

The following shows that:

  1. There is a penalty for going first (either traditional or fat)
  2. There is no discernible difference in Chrome

function goFat() {
    for (var i = 0; i < 1000000; i++) {
        var v = ()=>{};
        v();
    }
}

function goTraditional() {
    for (var i = 0; i < 1000000; i++) {
        var v = function() {};
        v();
    }

}

function race() {
  var start = performance.now();
  goTraditional();
  console.log('Traditional elapsed: ' + (performance.now() - start));
  start = performance.now();
  goFat()
  console.log('Fat elapsed: ' + (performance.now() - start));
  start = performance.now();
  goTraditional();
  console.log('Traditional elapsed: ' + (performance.now() - start));
  start = performance.now();
  goFat()
  console.log('Fat elapsed: ' + (performance.now() - start));
  console.log('------');
}
<button onclick="race()">RACE!</button>
Ben Aston
  • 49,455
  • 61
  • 188
  • 322
  • 18
    There’s a massive penalty because you start the first timer before the button is clicked… – Ry- May 17 '17 at 17:12
  • 3
    When you don't know what happens behind the scene in v8 (and I really don't know I've read source code of v8, but I don't expert anyway) you can't interpret results in right way because of v8 optimizations. Check this article for some examples http://mrale.ph/blog/2012/12/15/microbenchmarks-fairy-tale.html – Alexander Myshov May 17 '17 at 17:16
  • Fair enough, but if you have to worry about this haven't you already answered your question? If the difference is undetectable in practice then asking about the theory is moot (unless you are interested in the difference in mechanics, which is not what you asked). – Ben Aston May 17 '17 at 17:21
  • 4
    @BenAston I just hope that some guru with deep v8 knowledge will see my question and will give an objective answer. :) – Alexander Myshov May 17 '17 at 17:25
  • OK fine. The spec defines the steps taken in the creation of both kinds of function. Some kind of finger in the air "this does more" might also be possible. – Ben Aston May 17 '17 at 17:25
  • Just throwing out that there's a noticeable difference in FF58. It certainly wouldn't make a difference in everyday usage, though. – jhpratt Feb 02 '18 at 05:10
  • **`There is a penalty for going first (either traditional or fat)`** should be in big bold – jave.web Mar 23 '22 at 20:20
3

I think arrow functions in class properties might cause some performance issue. Here is an example :

class Car {
  setColor = (color) => { this.color = color; }

  constructor() {
     this.color = '';
     this.getColor = () => { return this.color; };
  }

  printCarColor() {
     console.log(this.color);
  }
}
var c = new Car();
console.log(c);

If we take a look at the variable c you will notice that function setColor and getColor are created brand new for each instance, and each new copy is placed on each instance whereas function printCarColor is residing on the prototype.

If you want a thousand instances to each be able to make fixed-context method references, you're going to need a thousand separate methods (not one shared), and of course then you're going to have to store each of those thousand separate methods on the instances themselves, thereby defeating the whole point of the single shared prototype.

Rohan Veer
  • 839
  • 10
  • 19
3

I made a short benchmark in jsben.ch. I ran it many times it seems that arrow functions were in the most cases just tiny little bit faster than normal functions. Even once or twice normal function was faster... It looks like the difference is insignificant. So in short - if you don't need to mind context or this, just use whatever looks better for you ;)

https://jsben.ch/kJxPT

const a = (b, c) => b+c;

a(1,2);

VS

function a(b,c){
    return b+c;
}

a(1,2);

enter image description here

bukso
  • 718
  • 7
  • 20
1

There are two examples for nodejs:

function testFat(a, b) {
    return a + b;
}

let testArrow = (a, b) => a + b;

let t1 = process.hrtime();
let tmp1 = 0;
for (let i = 0; i < 1000000000; ++i) {
    tmp1 = testFat(tmp1, i);
}
var fatTime = process.hrtime(t1);
console.log('fat', fatTime);

let t2 = process.hrtime();
let tmp2 = 0;
for (let i = 0; i < 1000000000; ++i) {
    tmp2 = testArrow(tmp2, i);
}
var arrowTime = process.hrtime(t2);
console.log('arrow', arrowTime);
function testFat() {
    return 0;
}

let testArrow = () => 0;

let t1 = process.hrtime();
for (let i = 0; i < 1000000000; ++i) {
    testFat();
}
var fatTime = process.hrtime(t1);
console.log('fat', fatTime);

let t2 = process.hrtime();
for (let i = 0; i < 1000000000; ++i) {
    testArrow();
}
var arrowTime = process.hrtime(t2);
console.log('arrow', arrowTime);```

Results are:

bash-3.2$ node test_plus_i.js

fat [ 0, 931986419 ]

arrow [ 0, 960479009 ]

bash-3.2$ node test_zero.js

fat [ 0, 479557888 ]

arrow [ 0, 478563661 ]

bash-3.2$ node --version

v12.8.0

bash-3.2$

So you can see that there is no difference in function call overhead.

Alex D.
  • 19
  • 2
  • That is a great answer to the queestion. The overhead of binding this is probably to small to notice. It would need multiple runs and also runs witht the test loops in reversed order, though. Nobody knows what gc background is going on and messing with the results.. – theking2 May 11 '21 at 16:24
0

in my exp I have found that arrow functions do run faster than normal JS functions. Here is a small snippet in react which uses arrow and normal function. I found that the component using arrow functions runs a bit faster than the one having normal js function.

https://codepen.io/lokeshpathrabe/pen/qgzadx

class Fun extends React.Component {

  constructor(props){
    super(props);
    this.state = {start: new Date().getTime(),
                 end: new Date().getTime(),
                 number: 0};
    console.log('Function start: ', this.state.start);
    const fun = function(me){
      let n = me.state.number
      me.setState({
        ...me.state, end: new Date().getTime(), number: ++n
      })
    }
    this.interval = setInterval(fun, 1, this);
  }

  stop(){
    clearInterval(this.interval);
  }

  componentDidUpdate(){
    if((this.state.end - this.state.start) > 5000){
      console.log('Function end: ', this.state.end);
      clearInterval(this.interval)
    }
  }

  render() {
    return (
      <div>
        <h2>Counter with Function {this.state.number}</h2>
      </div>
    )
  }
}

class Arrow extends React.Component {

  constructor(props){
    super(props);
    this.state = {start: new Date().getTime(),
                 end: new Date().getTime(),
                 number: 0};
    console.log('Arrow start: ', this.state.start);
    this.interval = setInterval(()=>{
      let n = this.state.number
      this.setState({
        ...this.state, end: new Date().getTime(), number: ++n
      })
    }, 1);
  }

  stop(){
    clearInterval(this.interval);
  }

  componentDidUpdate(){
    if((this.state.end - this.state.start) > 5000){
      console.log('Arrow end: ', this.state.end);
      clearInterval(this.interval)
    }
  }

  render() {
    return (
      <div>
        <h2>Counter with Arrow {this.state.number}</h2>
      </div>
    )
  }
}

class HOC extends React.Component {

  render() {

    return (<div>
        <h1>The one reaching higher count wins</h1>
        <Arrow/>
        <Fun/>
        </div>);
  }
}

ReactDOM.render(<HOC />, document.getElementById('react-content'))

Do let me know if your opinion differ

Lokii
  • 332
  • 2
  • 8
0

An arrow function is just a function expression. The below are equal:

const foo = (a, b) => a + b // foo = an anonymous function
const foo = function(a, b) { return a + b; }
const foo = new Function("a", "b", "return a + b")

A function declaration can be hoisted:

function foo(a, b) { return a + b; }

An arrow function cannot be use as an generator function like:

function* foo(a, b) {
  yield a;
  yield b;
}

Consider to use them when your functions need this keyword.


There are at least not many differences in the performance.

Danny
  • 534
  • 5
  • 21