DEV Community

Arrow Functions vs. Normal Functions in JavaScript

Hritam Shrivatava on January 31, 2024

JavaScript, being a versatile and dynamic language, provides developers with various ways to define functions. Among these, two primary approaches ...
Collapse
 
miketalbot profile image
Mike Talbot ⭐

Should point out that one big advantage of arrow functions not having a this is that you can use the this of the enclosing context, normally a nightmare for sub-functions.

Collapse
 
_ndeyefatoudiop profile image
Ndeye Fatou Diop

Yep ! I always just arrow functions in classes . It avoids having to use « .bind(this) »

Collapse
 
peerreynders profile image
peerreynders

Please stop …

One disadvantage I feel is, we can not use arrow function to define constructor.

There are other disadvantages:

  • class methods are shared via the prototype object so there is only one function value per method per class.
  • arrow functions are function values bound to an instance field - so there is a function value dedicated to each object created by the class. This has implications if you plan to use inheritance.
class A {
  myArrow = () => {
    console.log('A.myArrow()');
  }

  myMethod() {
    console.log('A.myMethod()');
  }
}

class B extends A {
  myArrow = () => {
    super.myArrow(); // arrow function exists on 
                     // public class field of A but
                     // but is not available on 
                     // the prototype chain via `super`
    console.log('B.myArrow()');
  }

  myMix() {
    super.myArrow(); // same problem
    console.log('B.myMix()');
  }

  myMethod() {
    super.myMethod(); // just works
    console.log('B.myMethod()');
  }
} 

let myB = new B();

myB.myMethod(); // 'A.myMethod()'
                // 'B.myMethod()'
myB.myMix();    // Uncaught TypeError: (intermediate value).myArrow is not a function at B.myMix
myB.myArrow();  // Uncaught TypeError: (intermediate value).myArrow is not a function at B.myArrow
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

So only use arrow functions in classes when there is a very good reason to (i.e. to avoid having to use bind anyway).

Classes often use arrow functions as reusable event listeners. However EventTarget.addEventListener() can also accept objects as listeners as long as they implement EventListener.handleEvent().

React's synthetic event system doesn't support object event listeners however object event listeners can be used via useEffect().


Technically arrow functions cannot be named.

const randomize = () => Math.random();
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

All that is happening here is that an anonymous function expression is bound to a variable name. To get a named function expression:

const randomize = function randomize() {
  return Math.random();
};
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

What is the difference?
In the following the locally available function name fibRec is used:

const fn = function fibRec(n2, n1, i) {
  const next = n2 + n1;
  return i > 1 ? fibRec(n1, next, i - 1) : next;
}

const fib = function(n) {
  return n > 1 ? fn(0, 1, n - 1) : n > 0 ? 1 : 0;
}

console.log(fib(20)); // 6765
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

With arrow functions the variable name fn from the creating scope has to be used:

const fn = (n2, n1, i) => i > 1 ? fn(n1, n2 + n1, i - 1) : n2 + n1;

const fib = (n) => n > 1 ? fn(0, 1, n - 1) : n > 0 ? 1 : 0;

console.log(fib(20)); // 6765
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Contrast this with a function declaration which cannot be unnamed.

function randomize() {
  return Math.random();
};
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

The advantage of a function declaration is that it is hoisted to the top of the scope. This works:

const SOME_CONSTANT = 2 * Math.PI;

console.log(calculate(10)); // 62.83185307179586

function calculate(radius) {
  return SOME_CONSTANT * radius;
}
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

This does not work:

const SOME_CONSTANT = 2 * Math.PI;

console.log(calculate(10)); // Uncaught ReferenceError: Cannot access 'calculate' before initialization

const calculate = (radius) => SOME_CONSTANT * radius;
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

This doesn't work either:

const SOME_CONSTANT = 2 * Math.PI;

console.log(calculate(10)); // TypeError: calculate is not a function

var calculate = (radius) => SOME_CONSTANT * radius;
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

While var calculate is hoisted, the assignment isn't.

</div>
Enter fullscreen mode Exit fullscreen mode

We can also use the arrow functions as class methods.

Despite of what you may see in framework tutorials in terms of "Vanilla JS" that is considered bad form.

The issue is that in arrow functions this refers to the lexical scope that created the function.

As a consequence each object instance will create a new, separate function.

For a regular method a single function exists on the prototype object of all the object instances that share it - i.e. one single function is reused across all object instances. For traditional methods (including the short hand syntax) this (the function context) refers to the calling context.

So when using arrow functions for "regular methods" you are creating more functions than you need to.

Arrow functions in class declarations are justified when that function is intended to be used as a callback (e.g. event listener), i.e. it needs to be bound to (this of) that particular object instance.

Please have a look at:

Also ES6 is the legacy term for ES2015. There is no ES7. Since ES2015 TC39 has annually published updates to the spec, e.g. ES2016, ES2017, etc.

The additions (finished proposals) for each publication year are identified here.

Some people use "ES.Next" when Babel offers implementations of proposals before they reach stage 4 (finished).

var variables are globally scoped or accessible.

Inside a function they are scoped to the function - i.e. not globally accessible - that is what the "immediately invoked function expressions" IIFE are about. However var declarations are effectively hoisted to the top of the function - independent of where they appear inside the function.

A class is another type of function

There is a class declaration and class expression.

The MDN's statement:

Classes are in fact "special functions"

is a bit misleading. It's more accurate to say that classes are syntax sugar for constructor functions.

we use this keyword to refer to the current class.

"we use this keyword to refer to the current object (an instance of the class)."

And as already mentioned this has a different meaning in arrow and traditional methods:

  • arrow function - the object the function was created on
  • traditional method - the object the method is called on

The super method

super is a keyword. super([arguments]) is only valid in the constructor. super.name or super[name]can be used to access properties (and functions/methods) on the parent.

The spread operator is used for splitting up the values of an array

The spread syntax also works on object literals and can also be used to convert an iterable to arguments for a function call.

JSX supports spread attributes:

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

This operator is used to represent an infinite amount to arguments in a function.

Some people think of the rest syntax as "gather" because it collects the remaining arguments.

The rest syntax can also be used for Array destructuring and Object destructuring.

You can use "rest in object destructuring" to omit object properties.

let {b, c, ...redacted} = {a: 4, b: 3, c: 2, d: 1};
console.log(redacted); // {a: 4, d: 1}
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

Array functions

One issue is that these functions are limited to arrays rather than general iterables. While at times Array.from, Object.entries, Object.keys, and Object.values can be helpful often it's more performant to stick to using for..of.

</div>
Enter fullscreen mode Exit fullscreen mode

Thread Thread
 
_ndeyefatoudiop profile image
Ndeye Fatou Diop

Ah, I am afraid I won't stop 😅 and here is why :

  1. As mentioned, I use arrow functions inside classes because:
    • I generally don't do inheritance: it's super frustrating to test, sometimes trickier to change, etc. See here
    • I rarely use classes and when I do, those are generally singletons. So, there is not an issue with creating many objects. Instead, I rely on closure if needed and create just normal objects.
  2. For everything else, I use named functions. As you mentioned, named functions are clearer and they show up nicely in React Devtools 😅. I can also just move them down the file to keep the code easier to navigate. Here below all the examples where I use named functions:
    • inside hooks
React.useEffect(function myNiceName(){}, [])
Enter fullscreen mode Exit fullscreen mode
  • when doing memoization
const MyComponent = React.memo(function _MyComponent(){})
Enter fullscreen mode Exit fullscreen mode
  • ...anywhere except in classes or when I'm forced to use inheritance

BLUF is you have to choose your problem and I don't want "this" to be one of them 😉

Thread Thread
 
peerreynders profile image
peerreynders • Edited

I generally don't do inheritance …
… those are generally singleton

This raises the question: why use class at all?

Remember JavaScript is one of the few languages where you are not forced to use classes—you can actually work with objects directly.

James Coplien: The Architectural Voids of Change, The End User and Deep Knowledge - YouTube

Keynote på Software Architecture Open Space 2019, den 07/11-2019.Få invitationer til vores events: http://eepurl.com/gCwRTPKontakt os direkte: https://www.lu...

favicon youtube.com

… JavaScript programmers here? Here are my object-oriented programmers! They are writing objects. The rest of you are writing classes!

It seems that for your use cases a factory function creating a closure is much clearer approach than using class.

Collapse
 
hriztam profile image
Hritam Shrivatava

Us🤝

Collapse
 
hriztam profile image
Hritam Shrivatava

True AF man, That's why I always use arrow functions as well

Collapse
 
efpage profile image
Eckehard

The flexible syntax can be curse or blessing. As mentioned, bracket can be ommitted in some cases. This are different ways to implement the same function:

function square( x ){  return x * x }
const square = function(x){ return x * x }
const square = ( x ) => { return x * x }
const square = ( x ) => x * x
const square = x => x * x
Enter fullscreen mode Exit fullscreen mode

This can be confusing (but even handy) if you write things like this:

const genx = f => x => f(x)
const mysin = genx(Math.sin)
console.log(mysin(0.5)) // -> 0.479425538604203
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ohaddahan profile image
ohaddahan

Agree , that’s why the classic function with a clear declaration and return is my favorite.
No room for error , confusion , personal taste.
Also, match a common pattern across most other languages.

Collapse
 
efpage profile image
Eckehard

I wrote a post on this topic some time ago.

Thread Thread
 
ohaddahan profile image
ohaddahan

I agree, once you add typescript (which everyone should) the arrow syntax becomes much worse.

Thread Thread
 
efpage profile image
Eckehard

I´m not sure I like typescript. Why use "any"? It should be enough just do omit the type declaration.

Thread Thread
 
ohaddahan profile image
ohaddahan

Using any is pretty much a last resort .
Typescript , although it’s extremely weak compared to strongly typed compiled languages, is still a significant improvement over plain JavaScript.

It’s a very easy way to significantly reduce bugs.

Thread Thread
 
efpage profile image
Eckehard

I worked with Object pascal at fairly large projects, so I know the advantages of strong types. But a compiler does far more than just type checking. It will detect errors even in parts of the code that will never be visited (ok, and will tell you, that it´s unused). A lot of errors in Javascript pop up when the code is executed, and this is far too late.

Usually I add some type checks where necessary and go with the hints, VScode / TSlint will give me while editing. The number of errors that could be prevented using types is relatively low in my daily work. This might be different if you work with more people on the same proect.

Being typeless is not always a disadvantage, but you surely needs a different way of thinking at some points. The only real painpoint for me is refractoring, as it forces you to manually revisit all functions calls. With types, changes can be detected automatically in most cases.

Ideally, Javascript should be extended to optionally allow type definitions for variables, function results and parameter lists. This would be fully sufficient for me.

Collapse
 
hriztam profile image
Hritam Shrivatava

I mean if u can pick one style and use that, like I use this

const square = () => {
    // Code
}
Enter fullscreen mode Exit fullscreen mode

Although I agree with you when multiple devs are working on a project it might get a little messy or confusing

Collapse
 
ohaddahan profile image
ohaddahan

Hard disagree on being more concise.
The classic function defenition is much clearer in syntax , arguments /output.

Not to mention that if you want to search your code for a function , with arrow functions it’s much more annoying compared to just searching for function keyword.

Collapse
 
hriztam profile image
Hritam Shrivatava

I agree with the search part, although for smaller or one line expressions, the arrow function saves both lines of code but it also makes it easier when multiple small functions are declared for me atleast

Collapse
 
ohaddahan profile image
ohaddahan

I don’t see the saving , you replace function keyword with a const binding.
You can also write a classic function in one line.

The examples you used are mostly trivial , see how it looks with typed input / output , destruct of arguments. The syntax becomes much less readable.

Arrow functions are nice for anonymous functions.

Collapse
 
alexmustiere profile image
Alex Mustiere

To be complete about syntax clarity, you can also declare a function like this:

const add = function(a, b) {
  return a + b;
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
hriztam profile image
Hritam Shrivatava

Yeah right!

Collapse
 
maya_walk profile image
Maya Walker

Unfortunately, not everything was clear to me, I’m not a technical specialist! but sometimes I read materials on such topics to be comprehensively developed

Collapse
 
jeffchavez_dev profile image
Jeff Chavez

I am learning a lot from the comments as well.

Collapse
 
hriztam profile image
Hritam Shrivatava

Us

Collapse
 
jeffchavez_dev profile image
Jeff Chavez

Refreshing!

Collapse
 
bimohxh profile image
hxh

usually I will answer this question this way, normal funtion play the role of implementing classes in JavaScript. but arrow function is juat a pure function. no other meanings.