Suggest an editImprove this articleRefine the answer for “Differences between arrow Function, Function declaration and Function expression”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Arrow function vs function declaration vs function expression** - three syntaxes for defining a function, differing in hoisting behavior, `this` binding, and constructor support. ```javascript function decl() {} // hoisted, dynamic this const expr = function() {}; // not hoisted, dynamic this const arrow = () => {}; // not hoisted, lexical this, no new ``` **Key:** Arrow functions capture `this` from the surrounding scope and can't be used with `new`.Shown above the full answer for quick recall.Answer (EN)Image**Function declarations**, **function expressions**, and **arrow functions** are three ways to define a function in JavaScript, each behaving differently around hoisting, `this` binding, and constructor support. ## Theory ### TL;DR - Function declarations hoist fully - call them anywhere in their scope, even before they appear in the file - Function expressions and arrow functions stay in the TDZ until assignment - accessing them early throws a ReferenceError - Arrow functions have no own `this` - they capture it lexically from the surrounding scope at definition time - Arrow functions can't be used with `new` - they have no `[[Construct]]` internal method - Rule of thumb: declarations for top-level utilities, arrows for callbacks and handlers, expressions for IIFEs ### Quick example ```javascript // Declaration: hoisted, callable before definition sayHi(); // 'Hi!' - no error function sayHi() { console.log('Hi!'); } // Expression: TDZ with const - calling early throws // sayHiExpr(); // ReferenceError const sayHiExpr = function() { console.log('Hi from expression!'); }; // Arrow: lexical this - takes it from surrounding scope const obj = { name: 'World', greet: () => console.log('Hi ' + this.name) // undefined, not 'World' }; obj.greet(); // 'Hi undefined' ``` Only the declaration works before its line. The arrow's `this.name` is `undefined` because the arrow captured `this` from the outer module scope, not from `obj`. ### Key difference The split that matters is **hoisting**. Function declarations are parsed before any code runs - the entire body lands in the execution context's VariableEnvironment upfront, making the function callable from line one. Expressions and arrows only hoist the variable binding: `undefined` with `var`, or a TDZ block with `const`/`let`. Beyond that, arrows skip creating `this` and `arguments` entirely - both are inherited from the enclosing lexical scope via the scope chain. ### When to use - Top-level utility function needed anywhere in the file - function declaration - Callback, array method, or event handler - arrow function (short syntax, correct `this`) - IIFE or higher-order return where you want a named function in stack traces - function expression - Constructor called with `new` - function declaration or expression, never arrow - Event listener in a class component - arrow (binds `this` to the instance automatically) ### Comparison table | Characteristic | Function Declaration | Function Expression | Arrow Function | | --- | --- | --- | --- | | **Syntax** | `function name() {}` | `const name = function() {}` | `const name = () => {}` | | **Hoisting** | Full: entire body | Variable only (TDZ with `const`/`let`) | Variable only (TDZ with `const`/`let`) | | **`this` binding** | Dynamic (call-site) | Dynamic (call-site) | Lexical (enclosing scope) | | **`arguments` object** | Yes | Yes | No - use rest params (`...args`) | | **Constructor (`new`)** | Yes | Yes | No - throws TypeError | | **`super` in classes** | Yes | Yes | No | | **When to use** | Hoisted utilities, module exports | IIFEs, higher-order returns | Callbacks, `map`/`filter`, React handlers | ### How V8 handles this V8 parses declarations in the pre-execution phase, allocating the full function object in the VariableEnvironment before a single line runs. Expressions and arrows parse as literals during execution and bind to the declared variable only when that line is reached. Arrows skip creating `this` and `arguments` bindings entirely - when you reference `this` inside an arrow, the engine walks up the scope chain to find it in the enclosing LexicalEnvironment. ### Common mistakes **Calling an arrow before assignment:** ```javascript greet(); // ReferenceError: Cannot access 'greet' before initialization const greet = () => console.log('hi'); ``` `const` keeps `greet` in the TDZ. Move the call below the assignment, or use a function declaration. **Expecting dynamic `this` from an arrow method:** ```javascript const obj = { name: 'Alice', say: () => console.log(this.name) // undefined, not 'Alice' }; obj.say(); ``` The arrow grabbed `this` from the module scope, not from `obj`. Use a regular method shorthand: `say() { console.log(this.name); }`. **Using `arguments` inside an arrow:** ```javascript const sum = () => console.log(arguments); // ReferenceError or outer arguments sum(1, 2, 3); ``` Arrows have no `arguments` binding. Use rest parameters: `const sum = (...args) => args.reduce((a, b) => a + b, 0)`. **Arrow as constructor:** ```javascript const User = () => ({ name: 'Bob' }); new User(); // TypeError: User is not a constructor ``` No `[[Construct]]` means no `new`. Use `function User() {}` or `class User {}`. This one shows up in code reviews more often than you'd expect - usually when someone refactors a class method into an arrow and forgets to update the callers. **`super` in an arrow class field:** ```javascript class Child extends Parent { // arrow = () => super.method(); // SyntaxError method() { super.method(); } // correct } ``` ### Real-world usage - React - arrow class fields (`handleSubmit = () => {}`) to bind `this` to the instance without `.bind(this)` in the constructor - Express.js - arrows in route callbacks (`app.use((req, res, next) => {})`) for predictable `this` - Redux - arrow selectors (`const getUsers = state => state.users`) - Node.js streams - function declarations for reusable parsers called from multiple places - Lodash - named function expressions in `_.curry(function add(a, b) {})` so recursive calls work and stack traces stay readable ### Follow-up questions **Q:** What happens when a function expression is declared with `var` instead of `const`? **A:** The variable hoists as `undefined`. Calling it before the assignment line gives `TypeError: sayHi is not a function` - not a ReferenceError - because the variable exists but holds `undefined` at that point. **Q:** Why does arrow have no `prototype`? **A:** The spec doesn't allocate `prototype` on arrows because they can't use `[[Construct]]`. No reason to create a property that would never be used. **Q:** Does `this` change in a function declaration under strict mode? **A:** Yes. In strict mode, `this` inside a function called without an explicit receiver is `undefined`, not the global object. Arrows are unaffected since they don't create their own `this`. **Q:** Can you give an arrow function a name for better stack traces? **A:** When you assign an arrow to a named `const`, the engine infers that variable name as the function name and shows it in stack traces. But you can't write a named arrow directly the way you write `function log() {}`. **Q:** (Senior) In V8, how does TDZ behave differently for an immediately-invoked arrow with `const` vs a `var` function expression? **A:** `const f = (() => {})()` evaluates fine on that line - TDZ only blocks access to `f` before the line, not the expression itself. With `var f = (function(){})()`, the var hoists as `undefined`. Referencing `f` before the line returns `undefined`, and calling `f()` throws `TypeError: f is not a function` - a different error from TDZ's ReferenceError, which matters when debugging. ## Examples ### Hoisting in practice ```javascript // Declaration is available immediately console.log(add(2, 3)); // 5 function add(a, b) { return a + b; } // Expression with const: TDZ until assignment line // console.log(multiply(2, 3)); // ReferenceError const multiply = function(a, b) { return a * b; }; console.log(multiply(2, 3)); // 6 ``` `add` works on line 2 because the declaration was hoisted with its full body. The commented `multiply` call would throw - `const` keeps the binding in the TDZ until the assignment. ### Arrow function `this` in a React class component ```javascript class Button extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; // Expression: needs .bind(this) to get the right context this.handleClickExpr = function() { this.setState({ count: this.state.count + 1 }); }.bind(this); } // Arrow: captures this from class body, no bind needed handleClickArrow = () => { this.setState({ count: this.state.count + 1 }); }; render() { return <button onClick={this.handleClickArrow}>{this.state.count}</button>; } } ``` The expression version needs `.bind(this)` or it loses context when the browser calls it. The arrow captures `this` from the class definition scope automatically. This is the pattern pre-hooks React relied on for class event handlers. ### Arrow fails as constructor in a factory ```javascript function Decl() { this.type = 'declaration'; } const Expr = function() { this.type = 'expression'; }; const Arrow = () => {}; new Decl(); // { type: 'declaration' } new Expr(); // { type: 'expression' } new Arrow(); // TypeError: Arrow is not a constructor // Real-world pattern that breaks at runtime, not compile time function createInstance(Ctor) { return new Ctor(); } createInstance(Decl); // works createInstance(Arrow); // TypeError in production ``` `new Arrow()` throws because arrows have no `[[Construct]]` method. The dangerous part: there's no compile-time error. If you pass an arrow to a factory expecting a constructor, the crash surfaces at runtime.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.