My Logo

PUBLISHED JUNE 07, 2024

Teghen Donald Ticha
Lastly Updated: 3 months ago
Reading time 11 mins

What are Arrow Functions and How Are They Different from Regular Functions?

Learn the key differences between arrow functions and regular functions in JavaScript, including syntax, this binding, arguments, and various use cases.
What are Arrow Functions and How Are They Different from Regular Functions?

Prerequisite

Before exploring arrow functions, ensure you have a basic understanding of:

  • JavaScript Variables and Functions: Familiarity with var, let, const, and how to declare and call functions.
  • Scope and this: Understanding of global/local scope and the this keyword.
  • ES6 Syntax: Basic knowledge of ES6 features, as arrow functions are part of ES6.
Brief Overview of JavaScript Functions

Functions are a fundamental part of JavaScript, allowing developers to encapsulate code into reusable blocks.

A function can be defined once and executed multiple times with different inputs.

Functions in JavaScript are first-class citizens, meaning they can be assigned to variables, passed as arguments to other functions, and returned from other functions.

Traditional functions can be defined using `function declarations` or `function expressions`.

Example of a Regular Function:

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

const fnExpression = function(a, b) {
  return a - b;
};
console.log('Sum 2 and 2 :', declaredFn(2, 2), 'Subtract 5 from 10: ', fnExpression(10, 5)); // outputs =>  Sum 2 and 2 : 4 Subtract 5 from 10:  5


Introduction to Arrow Functions in ES6

Arrow functions were introduced in ECMAScript 6 (`ES6`) as a shorter and more concise way to write functions.

They provide a more compact syntax compared to regular functions and offer benefits such as lexical scoping of the this keyword.

Example of an Arrow Function:

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

const subtract = (a, b) => a - b ;
  
console.log('Add 2 and 2 :', add(2, 2), 'Subtract 5 from 10: ', subtract(10, 5)); // outputs =>  Add 2 and 2 : 4 Subtract 5 from 10:  5

Syntax of Arrow Functions

Arrow functions offer a more concise syntax compared to regular functions. The syntax consists of a parameter list, followed by the arrow (=>), and then the function body.

Basic Syntax:

const functionName = (parameters) => {
    // function body
};



For single parameters, parentheses can be omitted. For functions with no parameters or multiple parameters, parentheses are required.

If the function body contains a single expression, curly braces and the return keyword can be omitted, and the expression will be implicitly returned.

Syntax Variations:
  • No Parameters:
const greet = () => console.log("Hello, world!");
greet(); // outputs => Hello, world!

  • Single Parameter:
const greet = (name) => console.log("Hello!", name);
greet('Yohan'); // outputs => Hello! Yohan

  • Multiple Parameters:
const greet = (name, message) => console.log("Hello!", name, message);
greet('Yohan', 'It will be exciting to meet you someday!🤩'); // outputs => Hello! Yohan It will be exciting to meet you someday!🤩

  • Block Body:
const greet = (name, message) => {
  console.log("Hello!", name, message);
  return true;
};
console.log('Is greeted: ', greet('Yohan', 'Living is playing!🧒')); 
/*outputs => Hello! Yohan Living is playing!🧒
            Is greeted:  true
*/


Examples of Arrow Functions

Here are some practical examples of using arrow functions in different contexts:

  • Simple Mathematical Operations:
const add = (a, b) => a + b;
console.log(add(5, 3)); // Output: 8

const double = n => n * 2;
console.log(double(4)); // Output: 8

  • Array Methods:

Arrow functions are often used as callbacks for array methods like map, filter, reduce, etc...

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Using map to create a new array with each number doubled
const doubled = numbers.map(n => n * 2);
console.log(doubled); // Output: [2,  4,  6,  8, 10, 12, 14, 16, 18, 20]

// Using filter to create a new array with only even numbers
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // Output: [ 2, 4, 6, 8, 10 ]

  • Event Handlers:

Arrow functions can be used in event handlers both in broswer and node evironments.

// node.js event example
const Event = require('events');

const ee = new Event();

ee.on('life_lesson', data => { // event handler
  console.log('Message: ', data);
})

ee.emit('life_lesson', 'Life\'s a just GAME!');



// browser events example (Assuming we have a html form with a submit button)
document.getElementById('button').addEventListener('click', () => {
  console.log('Button clicked! Submit fomr');
});

  • Implicit Return with Single Expression:
const greet = name => `Hello, ${name}!`;
console.log(greet('Yohan')); // Output: Hello, Yohan!


These examples demonstrate the simplicity and power of arrow functions in various scenarios, making them a valuable addition to the JavaScript language.

1. Syntax

Arrow functions provide a more concise and readable syntax compared to regular functions.

They remove the need for the function keyword, and in the case of single expressions, they also remove the need for curly braces and the return keyword.

// Regular function
const doubleFn = function(n) {
  return n * 2;
};

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

// Arrow function
const double = n => n * 2;
const add = (a, b) => a + b;


Regular functions require more verbose syntax, which can make code less readable, especially when defining small, one-line functions.

Arrow functions streamline this process, making the code more compact and easier to understand.

2. this Binding
Lexical Scoping of this in Arrow Functions

Arrow functions do not have their own this context. Instead, they inherit this from the surrounding scope at the time they are defined. This behavior is known as lexical scoping.

const obj = {
    value: 42,
    getValue: () => {
        return this.value; // `this` refers to the global object, not `obj`
    }
};

console.log(obj.getValue()); // Output: undefined

Comparison with Dynamic this in Regular Functions

Regular functions have their own this context, which is determined by how the function is called.

When a regular function is used as a method, this refers to the object that the method is called on.

const obj = {
  value: 42,
  getValue: function () {
      return this.value; // `this` refers to `obj`
  }
};

console.log(obj.getValue()); // Output: 42

Examples Illustrating this Binding in both cases.

function Namer (surName) {
  this.surName = surName;

  this.getName = () => {
    console.log(`The name is : ${this.surName}`)
  },

  this.getNameFn = function () {
    console.log(`The name is : ${this.surName}`);
  }
  
}
const namer = new Namer('Yohan');

namer.getName(); // Output: The name is : Yohan
namer.getNameFn(); // Output: The name is : Yohan

In this example, both getName and getNameFn methods work as expected because this in getName refers to the namer instance.

3. Arguments Object
Absence of arguments Object in Arrow Functions

Arrow functions do not have their own arguments object. If you need to access the arguments passed to an arrow function, you must use rest parameters.

const add = () => {
    console.log(arguments); // ReferenceError: arguments is not defined
};

add(1, 2);

Use of Rest Parameters as an Alternative

Rest parameters can be used to gather all arguments into an array, providing similar functionality to the arguments object.

const add = (...args) => {
    return args.reduce((sum, arg) => sum + arg, 0);
};

console.log(add(1, 2, 3)); // Output: 6

Comparison with Regular Functions

Regular functions have access to the arguments object, which contains all the arguments passed to the function.

function add() {
    return Array.from(arguments).reduce((sum, arg) => sum + arg, 0);
}

console.log(add(1, 2, 3)); // Output: 6

4. Constructor Usage
Arrow Functions Cannot Be Used as Constructors

Arrow functions cannot be used as constructors and will throw an error if used with the new keyword.

const Person = (name) => {
    this.name = name;
};

const yohan = new Person('Yohan'); // TypeError: Person is not a constructor


Regular functions can be used as constructors, allowing you to create instances of objects.

function Person(name) {
  this.name = name;
}

const yohan = new Person('Yohan');
console.log(yohan.name); // Output: Yohan

5. Methods in Objects
Arrow Functions and Object Methods

Arrow functions should not be used as methods in objects because they do not have their own this context.

Instead, they inherit this from the surrounding scope, which can lead to unexpected behavior.

const obj = {
    value: 42,
    getValue: () => this.value // `this` refers to the global object, not `obj`
};

console.log(obj.getValue()); // Output: undefined

Why Arrow Functions Are Not Suitable for Methods in Objects

Using regular functions as methods ensures that this refers to the object the method is called on, providing the expected behavior.

NB: To ensure correct this binding, use regular functions for methods within objects.


For example, let's create a calculator object with chainable methods.

const calculator = {
    value: 0,
    add(a) {
        this.value += a;
        return this;
    },
    subtract(a) {
        this.value -= a;
        return this;
    }
};

console.log(calculator.add(5).subtract(2).value); // Output: 3

6. Implicit Return
Single-Line Arrow Functions with Implicit Return

Arrow functions allow for an implicit return when the function body is a single expression, eliminating the need for the return keyword.

const add = (a, b) => a + b;  // implicit return
console.log(add(3, 4)); // Output: 7


Regular functions require the return keyword to return a value, even for single expressions.

function add(a, b) {
    return a + b; //  explicit return
}

console.log(add(3, 4)); // Output: 7

As you can observe even from this very simple example, using implicit return in arrow functions can make the code more concise and readable.

Arrow functions shine in scenarios where concise syntax and lexical this binding are advantageous. Here are some common use cases:

  • Callbacks in Array Methods: Arrow functions are often used as callbacks for methods like map, filter, and reduce due to their shorter syntax.
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // Output: [2, 4, 6, 8, 10]

  • Event Handlers: Arrow functions are useful in event handlers, especially when using modern frameworks like React, where preserving the context of this is crucial.
document.getElementById('button').addEventListener('click', () => {
    console.log('Button clicked!');
});

  • Short Functions: For simple one-liners and utility functions, arrow functions provide a clean and concise way to write code.
const square = x => x * x;
console.log(square(5)); // Output: 25

  • Lexical this: In scenarios where you need to maintain the this context from the surrounding scope, arrow functions are ideal.
function Timer() {
  this.seconds = 0;
  const  t = setInterval(() => {    
      this.seconds++;
      console.log(this.seconds);
      if (this.seconds === 10) { // clears the timer after ~ 10 seconds
        clearInterval(t)
      }
  }, 1000);
}
const timer = new Timer();

Performance Considerations

While arrow functions offer syntactical advantages, it's important to consider performance implications:

  • Memory Usage: Arrow functions can be more memory efficient when used within methods that are called frequently because they avoid creating a new this context.
// with arrow function
class Counter1 {
  constructor() {
      this.count = 0;
  }
  increment() {
      setTimeout(() => {
          this.count++;
          console.log('With Arrow Fn: ', this.count);
      }, 1000);
  }
}
const counter1 = new Counter1();
counter1.increment(); // Output With Arrow Fn:  1



// with regualr function
class Counter2 {
  constructor() {
      this.count = 0;
  }
  increment() {
      const that = this
      setTimeout(function () {
        that.count++;
          console.log('With Regular Fn: ', that.count);
      }, 1000);
  }
}
const counter2 = new Counter2();
counter2.increment(); // Output With Regular Fn:  1

  • Function Creation Overhead: In tight loops or performance-critical code, creating many arrow functions might incur a slight performance overhead compared to reusing a single function. However, in most cases, this difference is negligible.


Conclusion

Arrow functions in JavaScript offer a concise and elegant syntax, making code more readable and maintainable in many scenarios.

They provide lexical this binding, which eliminates the need for explicit bind, call, or apply methods.

However, it's essential to recognize that arrow functions are not always the best choice and that regular functions still have their place, particularly in contexts where dynamic this binding or access to the arguments object is necessary.


By understanding the differences between arrow functions and regular functions and their respective use cases, you can make informed decisions about when to use each type of function.

When used appropriately, arrow functions can enhance code clarity and improve your overall productivity.


Further Reading

Explore these resources to deepen your understanding of arrow functions and JavaScript function syntax.

Interested in exploring more 🤓? Check out these Related Posts.