JavaScript this Keyword
Context is Everything
Explanation
What is 'this'?
The this keyword refers to the object that is executing the current function. Its value is determined by how a function is called, not where it's defined.
Key Rules
- Global context:
this= window (browser) or global (Node) - Object method:
this= the object - Constructor:
this= new instance - Explicit binding:
this= specified object - Arrow function:
this= lexical (inherited)
Demonstration
Example 1: Global and Function Context
// Global context (non-strict mode)
console.log(this); // window (browser) or global (Node)
// Strict mode
'use strict';
function strictFunc() {
console.log(this); // undefined
}
strictFunc();
// Non-strict mode
function regularFunc() {
console.log(this); // window/global
}
regularFunc();
// Global variables become window properties
var globalVar = 'I am global';
console.log(window.globalVar); // 'I am global'
console.log(this.globalVar); // 'I am global'
// let/const don't attach to window
let notGlobal = 'Not on window';
console.log(window.notGlobal); // undefined
Example 2: Object Methods
const user = {
name: 'Arthur',
greet() {
console.log(`Hello, I'm ${this.name}`);
},
nested: {
name: 'Nested',
greet() {
console.log(`Hello from ${this.name}`);
}
}
};
user.greet(); // "Hello, I'm Arthur"
user.nested.greet(); // "Hello from Nested"
// Method assignment loses context
const greetFunc = user.greet;
greetFunc(); // "Hello, I'm undefined" (or error in strict mode)
// Passing as callback loses context
setTimeout(user.greet, 100); // "Hello, I'm undefined"
// Fix with bind
setTimeout(user.greet.bind(user), 100); // "Hello, I'm Arthur"
// Fix with arrow function wrapper
setTimeout(() => user.greet(), 100); // "Hello, I'm Arthur"
// Method shorthand vs function property
const obj = {
// Method shorthand - 'this' works
method() {
return this;
},
// Arrow - 'this' is lexical (outer scope)
arrow: () => {
return this;
}
};
console.log(obj.method() === obj); // true
console.log(obj.arrow() === obj); // false (window or undefined)
Example 3: Constructor Functions and Classes
// Constructor function
function Person(name) {
this.name = name;
this.greet = function() {
console.log(`Hi, I'm ${this.name}`);
};
}
const person1 = new Person('Arthur');
person1.greet(); // "Hi, I'm Arthur"
// Without 'new' - disaster!
const person2 = Person('Oops'); // 'this' is window/global
console.log(person2); // undefined
console.log(window.name); // 'Oops' - polluted global!
// Class syntax
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}`);
}
// Arrow method - bound to instance
greetArrow = () => {
console.log(`Hello, ${this.name}`);
};
}
const user = new User('Arthur');
const greet = user.greet;
const greetArrow = user.greetArrow;
greet(); // undefined (lost context)
greetArrow(); // "Hello, Arthur" (arrow keeps context)
Example 4: Explicit Binding (call, apply, bind)
function introduce(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const arthur = { name: 'Arthur' };
const sarah = { name: 'Sarah' };
// call - invoke immediately with arguments
introduce.call(arthur, 'Hello', '!'); // "Hello, I'm Arthur!"
introduce.call(sarah, 'Hi', '.'); // "Hi, I'm Sarah."
// apply - invoke immediately with array of arguments
introduce.apply(arthur, ['Hey', '!!']); // "Hey, I'm Arthur!!"
// bind - return new function with bound 'this'
const arthurIntro = introduce.bind(arthur);
arthurIntro('Greetings', '~'); // "Greetings, I'm Arthur~"
// bind with preset arguments (partial application)
const arthurHello = introduce.bind(arthur, 'Hello');
arthurHello('!'); // "Hello, I'm Arthur!"
// bind is permanent - can't rebind
const boundOnce = introduce.bind(arthur);
const boundTwice = boundOnce.bind(sarah);
boundTwice('Hi', '!'); // "Hi, I'm Arthur!" (still arthur)
// Practical: Event handlers
class Button {
constructor(label) {
this.label = label;
}
handleClick() {
console.log(`${this.label} clicked`);
}
}
const btn = new Button('Submit');
// Without bind: 'this' would be the DOM element
document.querySelector('button')
.addEventListener('click', btn.handleClick.bind(btn));
Example 5: Arrow Functions
// Arrow functions inherit 'this' from enclosing scope
const obj = {
name: 'Arthur',
regularMethod() {
console.log(this.name); // 'Arthur'
// Regular function in method - loses 'this'
function inner() {
console.log(this.name); // undefined
}
inner();
// Arrow function preserves 'this'
const innerArrow = () => {
console.log(this.name); // 'Arthur'
};
innerArrow();
},
// Arrow as method - inherits outer 'this' (usually wrong!)
arrowMethod: () => {
console.log(this.name); // undefined (outer scope)
}
};
obj.regularMethod();
obj.arrowMethod();
// Arrow functions great for callbacks
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// Arrow preserves 'this'
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
}
// Arrow functions can't be bound
const arrow = () => this;
console.log(arrow.call({ name: 'test' })); // window (ignored)
Example 6: Common Patterns and Gotchas
// this in callbacks
const obj = {
items: [1, 2, 3],
// forEach with arrow - works
sumArrow() {
let sum = 0;
this.items.forEach(item => {
sum += item * this.multiplier;
});
return sum;
},
// forEach with regular function - fails
sumRegular() {
let sum = 0;
this.items.forEach(function(item) {
sum += item * this.multiplier; // this.multiplier undefined
});
return sum;
},
// Fix: pass thisArg to forEach
sumFixed() {
let sum = 0;
this.items.forEach(function(item) {
sum += item * this.multiplier;
}, this); // Pass 'this' as second argument
return sum;
},
multiplier: 2
};
// 'that' / 'self' pattern (old-school)
function OldClass() {
const self = this;
this.value = 42;
setTimeout(function() {
console.log(self.value); // 42 (using closure)
}, 100);
}
// Chaining with 'this'
class Builder {
constructor() {
this.value = '';
}
add(str) {
this.value += str;
return this; // Enable chaining
}
build() {
return this.value;
}
}
const result = new Builder()
.add('Hello')
.add(' ')
.add('World')
.build(); // 'Hello World'
Key Takeaways:
thisis determined by how a function is called- Object methods:
this= the object - Arrow functions:
this= lexical scope - Use bind/call/apply for explicit binding
- Arrow functions can't be rebound
Imitation
Challenge 1: Fix the Context
Task: Fix this code so counter.increment() works correctly when passed to setInterval.
const counter = {
count: 0,
increment() {
this.count++;
console.log(this.count);
}
};
// This doesn't work - fix it
setInterval(counter.increment, 1000);
Solution
// Solution 1: Use bind
setInterval(counter.increment.bind(counter), 1000);
// Solution 2: Arrow function wrapper
setInterval(() => counter.increment(), 1000);
// Solution 3: Define increment as arrow function
const counter = {
count: 0,
increment: function() {
this.count++;
console.log(this.count);
}.bind(this) // Won't work - this is wrong at definition time
};
// Better: Arrow in class
class Counter {
count = 0;
increment = () => {
this.count++;
console.log(this.count);
};
}
const c = new Counter();
setInterval(c.increment, 1000); // Works!
Practice
Exercise 1: Implement bind
Difficulty: Intermediate
Implement your own myBind function:
Function.prototype.myBind = function(context, ...args) {
// Your implementation
};
Exercise 2: this in Event Handlers
Difficulty: Advanced
Create a component class where event handler methods maintain correct this context without arrow functions or bind in constructor.
Summary
What you learned:
- How
thisis determined by call-site - Object methods and context loss
- Explicit binding with call/apply/bind
- Arrow function lexical
this - Common patterns and solutions
Next Steps:
- Read: Closures
- Practice: Refactor class methods
- Explore: Proxy and Reflect
