👋 Hi! I'm Dastan, author of Hack Frontend. Always open to professional networking => connect with me on LinkedIn.

Arrow Functions vs Function Declaration vs Function Expression

In JavaScript there are several ways to declare functions: Function Declaration, Function Expression and Arrow Function. Each has its own features regarding hoisting, this usage, ability to be a constructor, and nuances in syntax and application.

Brief Comparison Table

CharacteristicFunction DeclarationFunction ExpressionArrow Function
Syntaxfunction name() { ... }const name = function() { ... }const name = () => { ... }
HoistingFull (the function can be called before its declaration)The function itself does not hoist. var: the variable is undefined until assignment. const/let: TDZ (ReferenceError) until initializationSame as an expression: it depends on var, const, or let, not on arrow syntax
Own thisYes (depends on call)Yes (depends on call)No (lexical this, inherited from outer scope)
argumentsAvailableAvailableNo (use ...rest)
Use with newYes (can act as constructor)Yes (if declared as regular function)No (Arrow Function can't be constructor)
ApplicationGeneral functions, constructorsClosures, callbacksOne-liners, callbacks

Details and Examples

Hoisting

  • Function Declaration fully hoists. This means we can call such function even before its actual declaration in file.

    hello(); // "Hello from Declaration!"
    
    function hello() {
      console.log("Hello from Declaration!");
    }
    
  • A Function Expression does not hoist as a ready function. Only the variable binding is created, and access before initialization depends on var, let, or const.

    With var, the variable hoists and is undefined until assignment: calling it throws a TypeError. With const or let, access before the initialization line hits the temporal dead zone and throws a ReferenceError.

    console.log(typeof sayHiVar); // "undefined"
    // sayHiVar(); // TypeError: sayHiVar is not a function
    
    var sayHiVar = function () {
      console.log("Hello from Expression!");
    };
    
    // sayHi(); // ReferenceError: Cannot access 'sayHi' before initialization
    
    const sayHi = function () {
      console.log("Hello from Expression!");
    };
    
  • An Arrow Function follows the same hoisting rules as a function expression: the variable hoists, not the arrow itself. With const/let you get a TDZ before initialization; with var the value is undefined.

    // greet(); // ReferenceError: Cannot access 'greet' before initialization
    
    const greet = () => console.log("Hello from Arrow!");
    

this Context

  • Function Declaration and Function Expression (if not arrow) have this context determined by call method (or .call, .apply, .bind binding).

  • Arrow Function doesn't have own this: it's lexically borrowed from scope where arrow function is declared.

    const user = {
      name: "Alice",
      sayHello: function() {
        console.log("Hello, I'm " + this.name);
      },
      arrowHello: () => {
        console.log("Hello, I'm " + this.name);
      }
    };
    
    user.sayHello();    // "Hello, I'm Alice" - this = user
    user.arrowHello();  // "Hello, I'm undefined" - this taken from outer scope (global / undefined)
    

arguments and ...rest Operator

  • Function Declaration and Function Expression have pseudo-array arguments containing all function arguments.

  • Arrow Function doesn't have arguments, but you can use ...rest operator to get all arguments.

    function sumAll() {
      let total = 0;
      for (const arg of arguments) {
        total += arg;
      }
      return total;
    }
    
    const sumAllArrow = (...args) => {
      return args.reduce((acc, curr) => acc + curr, 0);
    };
    

Using with new

  • Only Function Declaration and Function Expression (not in arrow format) can be constructors.
  • Arrow Function can't be used with new as it doesn't have internal [[Construct]] mechanism.

By continuing to use the platform, you accept the terms of the Privacy Policy and the use of cookies.