95

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,881
  • 2
  • 20
  • 31
  • 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
  • 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
  • Does this answer your question? [Binding vs Arrow-function (in JavaScript, or for react onClick)](https://stackoverflow.com/questions/50375440/binding-vs-arrow-function-in-javascript-or-for-react-onclick) – Top-Master Mar 12 '23 at 04:55

8 Answers8

210

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

Mathias Bynens
  • 144,855
  • 52
  • 216
  • 248
jmrk
  • 34,271
  • 7
  • 59
  • 74
  • 8
    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). –  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
  • 25
    @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
  • 10
    @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
  • 7
    @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
  • 2
    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
  • 1
    So I decided to check the V8 code and while spec says there's less to do (no `constructor`, no `arguments`), V8 doesn't actually perform any work unless you actually try to access them. At most it allocates a pointers, which is basically free. https://github.com/v8/v8/blob/b1f450a1a6d0dd3e1ad6d590ea54b12c4ae5d4d0/src/ast/scopes.cc#L2443 – ShortFuse Nov 25 '22 at 21:08
9

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
  • 1,374
  • 14
  • 21
  • 3
    I'd actually love to see someone follow up on this with a test. – Ecksters Dec 31 '20 at 23:34
  • 10
    While you're right that this is a very inefficient pattern, it has nothing to do with the question, because it's the same for arrow functions and ordinary functions. If you wrote `this.getColor = function() { return this.color; }`, that would be just as bad. – jmrk Jun 13 '22 at 22:46
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
  • 53,718
  • 65
  • 205
  • 331
  • 21
    There’s a massive penalty because you start the first timer before the button is clicked… – Ry- May 17 '17 at 17:12
  • 4
    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 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
  • 1,108
  • 12
  • 23
  • 2
    You're just measuring random noise there. Maybe the ads on jsben.ch got in the way. There is no performance difference between `(b,c)=>b+c` and `function(b,c) { return b+c}`, if your test claims otherwise then your test is lying to you. – jmrk Jun 13 '22 at 23:02
  • @jmrk yes I mentioned that once the regular function was faster - so: no difference – bukso Jun 15 '22 at 11:57
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
  • 402
  • 4
  • 14
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
  • 883
  • 8
  • 33
0

I'd agree with jmrk's answer here but am reinforcing Rohan's answer.

It's worth noting that arrow functions are not only "syntactic sugar" when used in ES6 classes.

Arrow functions in classes aren't present on the prototype, but are created as a member for every object, every time that object's class is instantiated.

For example, take this class:

class ArrowFunctionMember {
    i = 0;

    test = () => {
        this.i += 1;
    }
}

That's the equivalent of doing:

class ArrowFunctionMemberEquivalent {
    i = 0;

    constructor () {
        this.test = function () {
            this.i += 1;
        }
    }
}

It's much easier to spot what's going on in the second example - When the class is instantiated an instance of the test function is created with it, this can lead to much larger memory consumption (and will be inherently slower to instantiate) depending on how many objects are created.

Here's a test I made to illustrate the performance difference between instantiation speed of classes with arrow function members vs with regular function members.

So if you're not creating many objects from classes with arrow function members, the performance difference will likely be negligible. However, if you are creating a large amount of objects then consider using regular member functions instead, so you can take advantage of the prototype sharing that JavaScript objects use.

SalemC
  • 28
  • 4
  • A few notes: (1) what you wrote here doesn't match your test: both cases you wrote here are equally bad, as both create a new function for every instance. (2) This has nothing to do with arrow or ordinary functions, if you wrote `this.test = function() { this.i += 1; }` that would be just as bad. (3) There are no ES5 classes at all, classes are an ES6 feature. – jmrk Jun 13 '22 at 22:57
  • @jmrk, the tests show the difference between instantiation speed for classes with arrow functions vs regular functions, isn't that the point I'm making? You're correct with 2, but that is exactly what an arrow function class member does as I've already stated, not what a regular function member does. You're also correct with 3, classes are an ES6 feature but they're more or less just functions, no? Either way, I used class syntax for POC - I'll update my answer for clarity. – SalemC Jun 15 '22 at 08:42
  • my point is: you still write "_arrow_ functions in classes aren't present on the prototype", but that's not because they're arrow functions: your updated snippet shows a regular function that _also_ isn't on the prototype. We're in total agreement that methods should be on the prototype, not on each instance -- but that's a different question from whether arrow functions or regular functions are faster (or have the same performance). (There's also a functional problem with arrow functions on instances btw, which is that they don't work correctly when called from subclasses.) – jmrk Jun 15 '22 at 15:44
  • @jmrk both snippets are meant to be the same thing, demonstrating why arrow functions aren't present on the prototype (context for people unfamiliar with how arrow functions work in classes). It's not related to the difference in speed of execution - as you've said, that's more or less identical. However, there is a clear difference in the memory implications and instantiation speed of classes that use arrow functions vs classes that don't, which _is_ relevant. I've not encountered that functional problem before though, I guess that's to do with context of the function when it's created? – SalemC Jun 15 '22 at 16:33
  • Again, you can put an arrow function on a prototype (though that causes issues: try `MyClass.prototype.method = () => this.i++` and see what happens), and you can put a non-arrow function on an instance. So "avoid arrow functions in classes" is not a good takeaway for avoiding the pitfall; "avoid putting (any!) functions on instances" is. – jmrk Jun 20 '22 at 10:23