1

I use the arrow function style quite alot in my ReactJS apps (all of them use functional style with hooks).

const handleKeyDown = (e) => {
    doStuff(e.keyCode);
}

Visual Studio Code keeps suggesting here, that I use a named function like:

function handleKeyDown(e) {
    doStuff(e.keyCode);
}

What's best practice here? I have read, that arrow function and named function are equivalent in javascript. Is this not true? Does using named function have any benefit here?

user6329530
  • 596
  • 1
  • 7
  • 21

2 Answers2

16

There are two major differences.

The first:

function handleKeyDown(e) {
    doStuff(e.keyCode);
}

Here you're using a function declaration (or function statement); in the case of arrow function:

const handleKeyDown = (e) => {
    doStuff(e.keyCode);
}

You're using a function expression. In fact, they're called arrow function expressions (since there is not an "arrow function declaration", their nature is to be an expression).

So to be clearer, the arrow function version it's more like:

const handleKeyDown = function(e) {
  doStuff(e.keyCode);
}

So the first difference is function declarations vs function expression, ( and therefore the difference between statement and expression: Javascript: difference between a statement and an expression?)

For what concerns the functions, function declarations are hoisted where function expressions are not. That's meaning that a code like that:

handleKeyDown({keyCode: 13});

function handleKeyDown(e) {
    doStuff(e.keyCode);
}

Is perfectly valid (and running), where a code like that:

handleKeyDown({keyCode: 13});

const handleKeyDown = (e) => {
    doStuff(e.keyCode);
}

Will raise an exception because handleKeyDown is not initialized yet.

Another difference between function expression and function declarations was that a function expression it was anonymous (unnamed). This is not valid anymore since the js engine is now able to determined the name associated with them:

const foo = () => {}
const bar = function() {}

console.log(foo.name) // "foo"
console.log(bar.name) // "bar"

However, the difference between arrow functions and function expressions, in this case, is that you can override it:

const bar = function baz() {};

console.log(bar.name) // baz

It's important to notice, however, that the name baz is scoped to the function's body block. It means is not available outside:

const bar = function baz() {
            console.log("my name is:", baz.name)
          }

console.log(bar.name) // "baz"
bar() // "my name is: baz"
baz() // ReferenceError: bar is not defined

This can be useful when you want to have a recursive function without pollute the outer scope with it. Common examples are setTimeout:

setTimeout(function foo() {
  doStuff();
  let time = calcNewTime();
  setTimeout(foo, time)
}, 1000);

or a RequestAnimationFrame:

requestAnimationFrame(function render() {
  updateCoordinates();
  renderGameScene();
  requestAnimationFrame(render);
});

Said that, the second major difference using arrow functions expressions instead of functions (expression or statement) is the nature of arrow functions itself. MDN has a neat list:

  1. Does not have its own bindings to this or super, and should not be used as methods.
  2. Does not have arguments (object), or new.target keywords.
  3. Not suitable for call, apply and bind methods, which generally rely on establishing a scope.
  4. Can not be used as constructors.
  5. Can not use yield, within its body.

The reason why they're not suitable for call, apply and bind is because they use the contextual object of the enclosing scope when defined; basically they don't have a this on their own. It's well seen when you use them as event handlers:

const obj = {
  handleClick() {
    console.log("I clicked ", this)
  }
}

button.addEventListener("click", obj.handleClick);
button.click(); // "HTMLButtonElement"

Where:

const obj = {
  handleClick: () => {
    console.log("I clicked ", this)
  }
}

button.addEventListener("click", obj.handleClick);
button.click(); // "Window", assuming you execute this code in a browser

To sum up, where there is definitely an area of overlapping between every way to define a callable function, each of them has its own characteristics to known in order to use them correctly, and avoid possible nasty bugs.

ZER0
  • 24,846
  • 5
  • 51
  • 54
2

Depends, in the majority of cases they do the same (arrow or named functions) BUT they have a difference, the arrow function doesn't have this, so it will look for in the closes parent scope that he can find it, but named functions have their own context.

Honestly, I don't know why VSCode is proposing that change, but depending if you are using class components or functional components using arrow functions can be better.

note, I don't use class components now, but this example is for the people still using that.

import React from 'react';

class SomeComponent extends React.Component {

  constructor(props) {
    super(props);
    this._clickHandlerNamed = this._clickHandlerNamed.bind(this);
    this.myText = 'Some text';
  }

  _clickHandlerNamed(e) {
    console.log(this.myText);
  }

  _clickHandlerArrow = e => console.log(this.myText);

  render() {
    return(
      <div>
        <button onClick={this._clickHandlerNamed}>Named Function</button>
        <button onClick={this._clickHandlerArrow}>Arrow Function</button>
      </div>
    );
  }
}

As you see in the constructor we are overriding the original this._clickHandlerNamed with a new function that instead of having his own this context, is using the context of the parent class, and this allows to access the this.myText property of the SomeComponent component.

Arrow functions don't have context, so is going to look for the closest this in the tree, so should be able to access the this.myText property.

Sebastián Espinosa
  • 2,123
  • 13
  • 23
  • Yes my whole app is functional. I am also defining the app like `const app = (props) => {}` (there VSC is suggesting the same change). Thus I don't need `this`. That's why I generally use arrow functions and have not noticed any obstacles concerning the scope. – user6329530 Oct 29 '20 at 09:19
  • keep doing that because in functional components there's no need to use this, never – Sebastián Espinosa Oct 29 '20 at 22:57
  • @user6329530 you're saying app is functional but you're avoiding using actual `function`s you know that sounds silly right?! – Vitim.us Apr 19 '23 at 13:34
  • @Vitim.us ... what – user6329530 Apr 20 '23 at 07:15
  • I just want to know _why_ vscode suggests things like this, I can't use "Refactor..." -> "Move to a new file" until I first convert to a named function... – Devin Rhode Jun 10 '23 at 19:15