Regular functions use the function keyword. Arrow functions use the => fat-arrow syntax and have several shorthand forms.
// Declaration function add(a, b) { return a + b; } // Expression const add = function(a, b) { return a + b; };
// Block body const add = (a, b) => { return a + b; }; // Concise body (implicit return) const add = (a, b) => a + b; // Single param — parens optional const double = n => n * 2;
const f = () => ({ key: val }); — without parens the {} is parsed as a block, not an object.
this BindingThis is the most consequential difference. Regular functions define their own this at call time. Arrow functions have no this of their own — they close over the this of the surrounding lexical scope at definition time.
thisconst obj = { name: 'obj', greet: function() { console.log(this.name); // 'obj' — this = obj } }; obj.greet(); // → 'obj' // Detach and call: this changes const fn = obj.greet; fn(); // → undefined (strict) or window.name (sloppy)
thisfunction Timer() { this.count = 0; setInterval(() => { this.count++; // this = Timer instance, always console.log(this.count); }, 1000); } new Timer(); // → 1, 2, 3 … // With a regular function callback this would be // undefined (strict) or window (sloppy) inside setInterval.
.call(), .apply(), .bind()These methods can rebind this on regular functions. On arrow functions they are syntactically valid but have no effect on this.
const arrow = () => this; const regular = function() { return this; }; arrow.call({ x: 1 }); // → outer this (unchanged) regular.call({ x: 1 }); // → { x: 1 }
arguments ObjectRegular functions receive an arguments array-like object automatically. Arrow functions do not — they inherit arguments from the enclosing scope, or it's undefined at the top level in strict mode.
function regular() { console.log(arguments); // Arguments [1, 2, 3] } regular(1, 2, 3); const arrow = () => { console.log(arguments); // ReferenceError in strict mode }; arrow(1, 2, 3); // Rest parameters work in both — prefer them over arguments const sum = (...args) => args.reduce((a, b) => a + b, 0);
Regular functions can be invoked with new. Arrow functions cannot — they throw a TypeError if you try.
function Person(name) { this.name = name; } const p = new Person('Alice'); // ✓ works const Animal = (name) => { this.name = name; }; const a = new Animal('Cat'); // ✗ TypeError: Animal is not a constructor
prototype PropertyRegular functions automatically have a prototype property (an object with a constructor back-reference). Arrow functions have no prototype property at all.
function Foo() {} console.log(Foo.prototype); // → { constructor: ƒ } const Bar = () => {}; console.log(Bar.prototype); // → undefined
Function declarations are fully hoisted — they can be called before the line they appear on. Function expressions (including arrow functions assigned to variables) follow the hoisting rules of their binding keyword (var, let, const).
sayHi(); // → 'hi' ✓ declaration is hoisted function sayHi() { console.log('hi'); } greet(); // ✗ ReferenceError: Cannot access 'greet' before initialization const greet = () => console.log('hello');
Because arrow functions capture this lexically, they are unsuitable as object methods when dynamic dispatch via this is needed. Use regular functions (or shorthand method syntax) for object methods and class methods.
// ✗ Arrow as method — this is the outer scope, not obj const obj = { val: 42, get: () => this.val // undefined }; // ✓ Regular function / method shorthand const obj = { val: 42, get() { return this.val; } // 42 }; // ✓ Arrow as class field — useful for event handler binding class Button { handleClick = () => { console.log(this); // always the instance }; }
asyncArrow functions cannot be generator functions (function* syntax is unavailable). Both regular and arrow functions can be async.
// Generator — only regular functions function* seq() { yield 1; yield 2; } // async — both work async function fetchData() { return await getData(); } const fetchData = async () => await getData();
| Feature | Regular Function | Arrow Function |
|---|---|---|
| this binding | Dynamic (call-site) | Lexical (definition-site) |
| arguments object | ✓ present | ✗ absent |
| new / constructor | ✓ allowed | ✗ TypeError |
| prototype property | ✓ present | ✗ absent |
| Hoisted (declaration form) | ✓ yes | ✗ no (expression only) |
| Generator (function*) | ✓ yes | ✗ no |
| async | ✓ yes | ✓ yes |
| Implicit return | ✗ no | ✓ concise body only |
| .call / .apply / .bind (this) | Effective | No effect on this |
| Suitable as object method | ✓ yes | ✗ generally no |
Callbacks and higher-order functions (.map, .filter, .reduce, Promise.then, setTimeout); any place where you want the surrounding this preserved without manual binding; short, single-expression functions where implicit return aids readability.
Object methods and class prototype methods where dynamic this dispatch is required; constructor functions (when not using ES6 classes); generators; top-level named utility functions where hoisting or a self-documenting stack trace name matters.