What is this?
this is a special keyword that refers to the object that is currently
executing the function. Unlike most languages where this is fixed to the
class instance, JavaScript's this is determined at call time
by four binding rules.
// Four rules that determine 'this':
// 1. Default binding - standalone function call -> global/undefined
// 2. Implicit binding - method call on an object -> that object
// 3. Explicit binding - call/apply/bind -> the specified object
// 4. new binding - constructor call -> new instance
// Arrow functions: inherit this from enclosing lexical scope (not one of the 4 rules)
// Simple demonstration
function whoAmI() {
return this;
}
const obj = { whoAmI };
console.log(whoAmI()); // global object (or undefined in strict mode)
console.log(obj.whoAmI()); // { whoAmI: [Function] } - the obj
Global Context
In the global scope (outside any function), this refers to the global object
(window in browsers, global in Node.js). In strict mode
standalone functions, this is undefined.
// In browser global scope
console.log(this === window); // true (browser)
// Standalone function - default binding
function standalone() {
console.log(this); // window (non-strict) or undefined (strict mode)
}
standalone();
// Strict mode
"use strict";
function strictFn() {
console.log(this); // undefined (not the global object)
}
strictFn();
// globalThis - unified reference across environments
console.log(globalThis); // window in browser, global in Node.js
Method Context (Implicit Binding)
When a function is called as a method of an object (obj.method()),
this refers to the object to the left of the dot.
const user = {
name: "Alice",
greet() {
return `Hello, I am ${this.name}`; // this = user
},
getName() {
return this.name; // this = user
}
};
console.log(user.greet()); // "Hello, I am Alice"
console.log(user.getName()); // "Alice"
// Nested objects
const company = {
name: "Ruban Tech",
ceo: {
name: "Bob",
greet() {
return `${this.name} from ${this.company}`; // this = ceo, not company!
}
}
};
console.log(company.ceo.greet()); // "Bob from undefined" (this = ceo object)
// this is lost when method is extracted
const greet = user.greet; // extract the method
console.log(greet()); // "Hello, I am undefined" - this is not user anymore!
Class Context (new Binding)
When a constructor function or class is called with new, this
refers to the newly created instance.
class Counter {
constructor(start = 0) {
this.count = start; // this = new instance
}
increment() {
this.count++; // this = the instance calling this method
return this; // return this for chaining
}
decrement() {
this.count--;
return this;
}
value() {
return this.count;
}
}
const c = new Counter(10);
console.log(c.increment().increment().decrement().value()); // 11
// c.count = 11 (10 +1 +1 -1)
// Old-style constructor function
function Person(name) {
this.name = name; // this = new Person instance
this.greet = function() {
return `Hi, I am ${this.name}`;
};
}
const alice = new Person("Alice");
console.log(alice.greet()); // "Hi, I am Alice"
Arrow Functions and this
Arrow functions do NOT have their own this. They inherit this
from the enclosing lexical scope. This makes them ideal for callbacks inside class methods.
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// PROBLEM with regular function - 'this' is undefined in callback
// setInterval(function() {
// this.seconds++; // TypeError! this is not Timer instance
// }, 1000);
// SOLUTION: arrow function inherits 'this' from start() method
setInterval(() => {
this.seconds++; // this = Timer instance (correct!)
console.log(this.seconds);
}, 1000);
}
}
// Arrow function as class field - permanently bound to instance
class Button {
constructor(label) {
this.label = label;
}
// Arrow function as a property - no binding issues
handleClick = () => {
console.log(`${this.label} clicked`); // this always = instance
}
}
const btn = new Button("Submit");
document.addEventListener("click", btn.handleClick); // safe - no bind needed
// Cannot change arrow function's this with call/apply/bind
const arrowFn = () => console.log(this);
arrowFn.call({ name: "Alice" }); // still logs outer 'this', not the passed object
In React class components, event handlers defined as arrow function class fields (handleSubmit = () => { this.setState(...) }) avoid the binding issue without needing .bind(this) in the constructor. In React function components with hooks, this is never needed at all.
call and apply
call and apply invoke a function immediately with an explicitly
specified this. The only difference is how arguments are passed.
function introduce(greeting, punctuation) {
return `${greeting}, I am ${this.name}${punctuation}`;
}
const alice = { name: "Alice" };
const bob = { name: "Bob" };
// call: arguments listed individually
console.log(introduce.call(alice, "Hello", "!")); // "Hello, I am Alice!"
console.log(introduce.call(bob, "Hi", ".")); // "Hi, I am Bob."
// apply: arguments as an array
console.log(introduce.apply(alice, ["Hello", "!"]));
console.log(introduce.apply(bob, ["Hi", "."]));
// Practical use: borrow methods from one object for another
const arr = [3, 1, 4, 1, 5, 9, 2, 6];
const max = Math.max.apply(null, arr); // spread array as args
console.log(max); // 9
// Modern equivalent: Math.max(...arr)
bind
bind returns a new function with this permanently set.
It does NOT call the function - it creates a bound copy.
function greet(greeting) {
return `${greeting}, ${this.name}!`;
}
const user = { name: "Alice" };
// bind returns a NEW function with 'this' fixed
const greetAlice = greet.bind(user);
console.log(greetAlice("Hello")); // "Hello, Alice!"
console.log(greetAlice("Hi")); // "Hi, Alice!"
// Partial application: pre-fill some arguments
function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2); // a = 2, null = no this needed
const triple = multiply.bind(null, 3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Binding in class constructor (older pattern)
class Counter {
constructor() {
this.count = 0;
// Bind once in constructor - works for event listeners
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.count++;
}
}
Common this Bugs and Fixes
// Bug 1: Method detached from object
const obj = {
name: "Alice",
greet() { return this.name; }
};
const greet = obj.greet;
console.log(greet()); // undefined - 'this' is not obj
// Fix: bind, arrow wrapper, or call with context
const greetBound = obj.greet.bind(obj);
console.log(greetBound()); // "Alice"
// Bug 2: Callback loses this
class Logger {
constructor() { this.prefix = "[LOG]"; }
log(msg) { console.log(`${this.prefix} ${msg}`); }
logAll(items) {
// BROKEN: items.forEach(this.log) - this is undefined
items.forEach(item => this.log(item)); // FIXED: arrow
}
}
// Bug 3: setTimeout with regular function
class Poller {
constructor() { this.data = []; }
start() {
// BROKEN: setTimeout(function() { this.data.push(...) }, 1000)
setTimeout(() => { // FIXED: arrow
this.data.push("new data");
}, 1000);
}
}