Arrow Functions & this Binding
Arrow Functions & this Binding
Arrow functions (=>) were introduced in ES6 and they're everywhere in modern JavaScript. But they're not just shorthand — they have a fundamentally different behavior around this that makes them essential to understand deeply.
Arrow Function Syntax
// Traditional function
function add(a, b) {
return a + b;
}
// Arrow function equivalents
const add = (a, b) => a + b; // implicit return
const add = (a, b) => { return a + b; }; // explicit return with braces
// Single parameter — parentheses optional
const double = n => n * 2;
const double = (n) => n * 2; // same thing
// No parameters — empty parentheses required
const sayHi = () => "Hello!";
// Returning an object — wrap in parentheses
const makeUser = (name, age) => ({ name, age });
// Without parens, the {} looks like a function body
Arrow Functions in Practice
const numbers = [1, 2, 3, 4, 5];
// Map
const doubled = numbers.map(n => n * 2);
// Filter + chain
const result = numbers
.filter(n => n > 2)
.map(n => n * n);
// Sort
const sorted = ["banana", "apple", "cherry"].sort((a, b) => a.localeCompare(b));
// Event handlers (in browser)
button.addEventListener("click", () => {
console.log("Clicked!");
});
// Promise chains
fetch("/api/user")
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
The this Problem in Regular Functions
this in JavaScript refers to the execution context — who called the function. This causes notorious bugs:
const timer = {
seconds: 0,
start() {
// setInterval calls this callback — but 'this' inside the callback
// refers to the global object (or undefined in strict mode), NOT timer
setInterval(function() {
this.seconds++; // BUG: this is wrong!
console.log(this.seconds);
}, 1000);
}
};
This was worked around with var self = this or .bind(this):
start() {
const self = this; // capture 'this' before it changes
setInterval(function() {
self.seconds++; // now it works
}, 1000);
// Or with .bind():
setInterval(function() {
this.seconds++;
}.bind(this), 1000);
}
How Arrow Functions Fix this
Arrow functions don't have their own this. They inherit this from the surrounding scope (lexical this):
const timer = {
seconds: 0,
start() {
// Arrow function uses the 'this' of start() — which is the timer object
setInterval(() => {
this.seconds++; // works correctly!
console.log(this.seconds);
}, 1000);
}
};
timer.start(); // logs 1, 2, 3...
When Arrow Functions Break
Arrow functions are NOT the right choice for object methods:
const user = {
name: "Alice",
// Arrow function as method — WRONG
greet: () => {
return `Hello, I'm ${this.name}`; // 'this' is NOT user here!
// In a browser: this is window (and window.name is "")
// In Node.js strict mode: this is undefined
},
// Regular function as method — CORRECT
greet() {
return `Hello, I'm ${this.name}`; // 'this' is user
}
};
Also wrong for constructors and prototype methods:
// Arrow functions can't be constructors
const Person = (name) => { this.name = name; };
new Person("Alice"); // TypeError: Person is not a constructor
// Don't use arrow for prototype methods
MyClass.prototype.method = () => {
this.value; // 'this' is wrong
};
Practical Rule
Use arrow functions for:
- Callbacks (setTimeout, map, filter, event handlers)
- Short utility functions
- Anywhere you want to preserve the outer
this
Use regular functions for:
- Object methods
- Constructor functions (though classes are preferred now)
- Functions that need their own
this
class EventEmitter {
constructor() {
this.listeners = [];
this.data = "some data";
}
// Regular function for the method itself
on(callback) {
this.listeners.push(callback);
}
// Regular function method that calls arrow function callbacks
emit() {
this.listeners.forEach(callback => {
// Arrow function preserves 'this' from emit()
callback(this.data);
});
}
}
call, apply, and bind
These three methods let you explicitly set this for regular functions:
function introduce(greeting, punctuation) {
return `${greeting}, I'm ${this.name}${punctuation}`;
}
const alice = { name: "Alice" };
const bob = { name: "Bob" };
// call — pass arguments individually
introduce.call(alice, "Hello", "!"); // "Hello, I'm Alice!"
introduce.call(bob, "Hi", "."); // "Hi, I'm Bob."
// apply — pass arguments as array
introduce.apply(alice, ["Hey", "~"]); // "Hey, I'm Alice~"
// bind — returns a NEW function with this permanently set
const aliceIntro = introduce.bind(alice);
aliceIntro("Hello", "!"); // "Hello, I'm Alice!"
// bind with pre-filled arguments (partial application)
const formalAlice = introduce.bind(alice, "Good day", ".");
formalAlice(); // "Good day, I'm Alice."
Note: You cannot change this with call, apply, or bind on arrow functions — they ignore it.
Next lesson: Destructuring — extracting values from arrays and objects into variables cleanly.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises