window in browsers and global in Node.js. In strict mode at the top level, and in all ES modules, this is undefined. The globalThis property (ES2020) provides a universal reference that works in every JavaScript environment.
// Browser (non-strict mode)
console.log(this === window); // true
// globalThis — works everywhere (ES2020)
console.log(globalThis); // window in browser, global in Node.js
// Strict mode or ES module top level
'use strict';
console.log(this); // undefined
// var declarations create window properties in browser
var x = 10;
console.log(window.x); // 10
console.log(this.x); // 10 (non-strict global only)
Why it matters: The global this is the baseline all other binding rules build upon. Knowing when it's window vs undefined vs module.exports prevents accidental global variable creation and helps debug errors in different runtime environments.
Real applications: Polyfills and isomorphic libraries use globalThis to safely attach properties that work in browser, Node.js, and Web Workers without environment-specific branching. Feature detection code checks typeof globalThis.fetch to avoid runtime errors.
Common mistakes: Accidentally creating global variables by assigning to unqualified names in non-strict mode (they become window properties), and forgetting that Node.js CommonJS modules have this === module.exports at the top level, not the global object.
this is the global object in sloppy mode or undefined in strict mode (default binding). Called as a method (obj.fn()), this is the object before the dot (implicit binding).
function showThis() { console.log(this); }
showThis(); // window (non-strict) or undefined (strict)
// Implicit binding — this = object before dot
const obj = { fn: showThis };
obj.fn(); // obj
// Same function, different 'this' based on call site
const fn = obj.fn;
fn(); // window or undefined (standalone)
'use strict';
function strictFn() { console.log(this); }
strictFn(); // undefined
Why it matters: The dynamic nature of this is a defining feature of JavaScript. It enables method sharing across objects via the prototype chain, but also causes the "lost this" bug when methods are passed as callbacks. Understanding dynamic binding is essential for all advanced JavaScript patterns.
Real applications: The prototype pattern relies on dynamic this — methods on Array.prototype work for any array instance because this is bound to the calling array at runtime. This is what makes [1,2,3].map(fn) work correctly for every array.
Common mistakes: Assuming this in a nested function refers to the outer function's this (each function call creates its own binding), and forgetting that strict mode makes default binding undefined instead of the global object, turning silent bugs into explicit TypeErrors.
this falls back to default binding.
const user = {
name: 'Alice',
greet() { return 'Hi, ' + this.name; }
};
user.greet(); // "Hi, Alice" — this = user
// Extraction loses 'this'
const greet = user.greet;
greet(); // "Hi, undefined" — this = window/undefined
// Nested: this = IMMEDIATE caller, not root object
const app = {
user: { name: 'Bob', getName() { return this.name; } }
};
app.user.getName(); // "Bob" — this = app.user, NOT app
// Passing as callback also loses binding
setTimeout(user.greet, 100); // "Hi, undefined"
Why it matters: "Lost this" from method extraction is one of the most common JavaScript bugs. It occurs when passing methods to event handlers, setTimeout, array callbacks, and framework lifecycle hooks. Recognizing it immediately saves significant debugging time.
Real applications: React class component event handlers must be bound in the constructor or use arrow class fields precisely because passing this.handleClick to an onClick prop extracts the method from its object, breaking the implicit binding.
Common mistakes: Calling setTimeout(obj.method, 0) expecting this to still be obj (it won't be), and assuming deep chaining like a.b.c.method() makes this === a when it's actually c.
this. They capture this from the enclosing lexical scope at definition time, and that binding is permanent — it cannot be overridden by call(), apply(), or bind(). This makes them ideal for callbacks preserving outer context, but unsuitable for object methods and prototype methods.
const obj = {
name: 'Alice',
regular() { return this.name; }, // "Alice" — own this
arrow: () => this.name, // undefined — outer this
delayed() {
setTimeout(() => {
console.log(this.name); // "Alice" — inherits method's this
}, 100);
}
};
// Cannot rebind arrow function's this
const arrow = () => this;
arrow.call({ a: 1 }); // still outer this, not { a: 1 }
class Timer {
constructor() { this.seconds = 0; }
start() {
setInterval(() => this.seconds++, 1000); // this = instance ✓
}
}
Why it matters: Arrow functions solved the most painful pre-ES6 JavaScript bug — callback this loss. Before arrow functions, every setTimeout and forEach callback needed .bind(this) or const self = this. Modern JavaScript uses arrow functions to eliminate this pattern entirely.
Real applications: React arrow function class fields (handleClick = () => {}) eliminate constructor binding entirely. Timer implementations in class components use arrow functions to keep this pointing to the instance without manual binding.
Common mistakes: Defining object methods as arrow functions ({ getName: () => this.name }) when this will be the outer scope instead of the object, and trying to .bind() an arrow function expecting it to change this (it silently has no effect).
.call(thisArg, ...args) invokes a function immediately with a specified this and individual arguments. .apply(thisArg, argsArray) does the same but accepts arguments as an array. .bind(thisArg, ...partialArgs) returns a new function with this permanently set — it never executes immediately and the binding cannot be overridden (except by new).
function greet(greeting, punct) {
return greeting + ', ' + this.name + punct;
}
const user = { name: 'Alice' };
greet.call(user, 'Hello', '!'); // "Hello, Alice!"
greet.apply(user, ['Hi', '.']); // "Hi, Alice."
const bound = greet.bind(user, 'Hey');
bound('?'); // "Hey, Alice?"
bound.call({ name: 'Bob' }, '!'); // "Hey, Alice!" — bind is permanent
// Method borrowing (classic pattern)
const args = Array.prototype.slice.call(arguments);
// Modern equivalent:
const args2 = [...arguments];
Why it matters: Explicit binding gives complete control over this when implicit binding isn't available. bind() is essential for creating callbacks with the correct context, and understanding it clarifies why Function.prototype.bind is one of the most polyfilled methods in JavaScript history.
Real applications: React class components use this.handleClick.bind(this) in constructors to fix event handler context. The classic Array.prototype.slice.call(arguments) pattern uses call() to borrow array methods for the array-like arguments object.
Common mistakes: Calling bind() in render methods instead of the constructor (creates a new function on every render, causing needless re-renders), and trying to rebind an already-bound function (the first bind() call's this is permanent — subsequent bind()/call() are ignored).
this).
class User {
constructor(name) {
this.name = name;
this.greetBound = this.greet.bind(this); // bind in constructor
}
greet() { return 'Hi, ' + this.name; }
greetArrow = () => 'Hi, ' + this.name; // arrow class field
}
const u = new User('Alice');
const fn1 = u.greet;
fn1(); // undefined or TypeError — 'this' lost
const fn2 = u.greetArrow;
fn2(); // "Hi, Alice" — arrow preserves 'this'
class MathHelper {
static create() { return new this(); } // this = class itself
}
Why it matters: This is the single most common source of bugs in React class components. Every event handler passed as a prop loses its this binding unless bound or written as an arrow class field. It's one of the most frequently asked React interview questions.
Real applications: In React class components, lifecycle methods work correctly because React calls them as instance.componentDidMount(). Event handlers need explicit binding because React calls them as standalone functions after extracting them from JSX props.
Common mistakes: Forgetting that arrow function class fields create a separate function per instance (stored on the instance, not the prototype), which can be a memory concern with huge numbers of instances. For performance-critical scenarios, constructor binding shares prototype methods.
addEventListener with a regular function, this refers to the element the listener is attached to (identical to event.currentTarget). Arrow functions inherit the outer scope's this instead — use event.currentTarget to access the element inside arrow callbacks.
const btn = document.querySelector('button');
// Regular function — this = element
btn.addEventListener('click', function(e) {
console.log(this); // <button> element
this.classList.add('active');
});
// Arrow function — this = outer scope
btn.addEventListener('click', (e) => {
console.log(this); // window or outer scope
e.currentTarget.classList.add('active'); // use currentTarget
});
// Class method handler
class App {
constructor() {
this.count = 0;
btn.addEventListener('click', this.handleClick.bind(this));
}
handleClick() { this.count++; }
}
Why it matters: Choosing between regular functions and arrow functions for event handlers directly impacts what this refers to inside the handler. As arrow functions become the default choice, understanding when to use event.currentTarget instead of this is increasingly important.
Real applications: Event delegation patterns attach one listener to a parent element — this/currentTarget is the parent, while event.target is the actual clicked child. This distinction is critical for building menus, tables, and lists with delegated click handling.
Common mistakes: Using arrow functions for event handlers and writing this.classList expecting it to be the element (it won't be), and attempting to remove event listeners by passing a new .bind() call (creates a different function reference — removeEventListener needs the exact same reference).
this using call(), apply(), or bind(), and it takes precedence over implicit binding. The binding priority hierarchy from highest to lowest is: new > explicit (call/apply/bind) > implicit (dot notation) > default (global/undefined).
function identify() { return this.name; }
const alice = { name: 'Alice' };
const bob = { name: 'Bob' };
// Explicit overrides implicit
identify.call(alice); // "Alice"
identify.call(bob); // "Bob"
// Bind wins over implicit dot notation
const bound = identify.bind(alice);
const obj = { name: 'Obj', fn: bound };
obj.fn(); // "Alice" — bind wins
// Passing null/undefined
// Non-strict: this = global Strict: this = null/undefined
identify.call(null); // window (non-strict) or null (strict)
// Priority: new > call/apply/bind > obj.method() > standalone
Why it matters: The precedence hierarchy makes this completely predictable in complex scenarios. Libraries that use call() internally, and code combining multiple binding methods at once, requires knowing which rule wins. This is a common senior-level interview topic.
Real applications: JavaScript's built-in methods use explicit binding internally — Array.prototype.forEach.call(nodeList, fn) sets this to an array-like, enabling array methods on non-arrays. Testing frameworks use .call(context) to run test specs in specific contexts.
Common mistakes: Passing null as the this argument to call() or apply() in non-strict mode (accidentally uses the global object instead of nothing), and not knowing that a function bound via bind() cannot be rebound — subsequent bind()/call()/apply() calls are silently ignored.
new, JavaScript: (1) creates a fresh empty object, (2) sets this to that object, (3) links its prototype to the constructor's prototype property, (4) runs the constructor body, and (5) returns this automatically — unless the constructor explicitly returns a different object. The new keyword has the highest this binding precedence, overriding even bind().
function Person(name) {
this.name = name; // new creates: this = Object.create(Person.prototype)
// implicitly: return this
}
const p = new Person('Alice');
console.log(p.name); // "Alice"
// new overrides bind
const BoundPerson = Person.bind({ name: 'Ignored' });
const p2 = new BoundPerson('Bob');
console.log(p2.name); // "Bob" — new wins over bind
// Returning a primitive is ignored
function Foo() { this.a = 1; return 42; }
new Foo(); // { a: 1 }
// Returning an object replaces 'this'
function Bar() { this.a = 1; return { b: 2 }; }
new Bar(); // { b: 2 } — this is discarded
Why it matters: Understanding the new 4-step process explains how constructor functions and ES6 classes work under the hood. It clarifies why returning an object from a constructor is valid (used for singletons), why instanceof works (prototype chain check), and what Object.create() replaces.
Real applications: The ES6 class syntax is syntactic sugar over functions and new. Understanding new's mechanism explains how super() must be called in subclass constructors before accessing this, and why Vue 2 used constructor functions for component creation.
Common mistakes: Forgetting new when calling a constructor function (in non-strict mode, this becomes the global object, polluting it with properties instead of creating an instance), and trying to use new with arrow functions (throws TypeError: X is not a constructor).
this falls back to default binding (global or undefined). This is the most common this-related bug in JavaScript, especially in setTimeout, addEventListener, array callbacks, and framework lifecycle hooks.
class Timer {
constructor() { this.seconds = 0; }
start() {
// Problem: 'this' is lost in a regular callback
setInterval(function() {
this.seconds++; // TypeError or NaN
}, 1000);
// Fix 1: arrow function (most common modern approach)
setInterval(() => { this.seconds++; }, 1000);
// Fix 2: bind
setInterval(function() { this.seconds++; }.bind(this), 1000);
// Fix 3: store reference (older ES5 pattern)
const self = this;
setInterval(function() { self.seconds++; }, 1000);
}
}
// Fix 4: arrow function class field
class Button {
label = 'Click';
handleClick = () => console.log(this.label); // always safe
}
Why it matters: Lost this is one of the most searched JavaScript bugs and a classic interview topic. Recognizing the pattern instantly and knowing all four fix strategies demonstrates practical mastery of JavaScript's this system beyond textbook knowledge.
Real applications: React class components required constructor binding for every event handler, or arrow class fields. The entire shift to functional components with hooks was partially motivated by eliminating this confusing pattern from everyday React development.
Common mistakes: Using bind() inside JSX (onClick={this.fn.bind(this)}) which creates a new function on every render causing performance issues, and using const self = this (the old ES5 workaround) instead of the cleaner arrow function solution available since ES6.
this binding — they do not inherit from the outer function's this. Each regular function call creates a new this based on how it is called, regardless of nesting depth. Arrow functions solve this by inheriting this from the enclosing scope instead of creating their own.
const person = {
name: 'Alice',
friends: ['Bob', 'Charlie'],
showFriends() {
// this = person (method call)
// Nested regular fn: this is LOST
this.friends.forEach(function(friend) {
console.log(this.name + ' knows ' + friend);
// this = undefined (strict) or window
});
// Fix 1: arrow function inherits outer 'this'
this.friends.forEach((friend) => {
console.log(this.name + ' knows ' + friend); // works
});
// Fix 2: thisArg parameter (2nd arg to forEach/map/filter)
this.friends.forEach(function(friend) {
console.log(this.name + ' knows ' + friend);
}, this); // second argument sets 'this' in the callback
}
};
Why it matters: Nested function this loss was one of the biggest ergonomic problems in pre-ES6 JavaScript. Arrow functions were introduced specifically to solve this. Understanding the problem makes arrow functions' design choices clear and memorable, not arbitrary.
Real applications: Any data transformation in a class method — mapping, filtering, or reducing arrays while accessing this.property — requires either arrow callbacks or the thisArg parameter. Without a fix, the inner callback silently uses the wrong this.
Common mistakes: Forgetting that .forEach(fn, this) (thisArg) only works for the immediate callback, not for further nested functions inside it. Arrow functions are preferred because they solve any nesting depth, not just one level deep.
globalThis (ES2020) provides a universal reference to the global object across all JavaScript environments. Before it, accessing the global object required environment-specific identifiers: window (browsers), global (Node.js), self (Web Workers) — or fragile try/catch detection patterns that could fail in edge cases.
// Works in browser, Node.js, Web Workers, Deno
console.log(globalThis);
// Pre-globalThis: fragile detection
const getGlobal = () => {
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof self !== 'undefined') return self;
};
// Modern: just use globalThis
globalThis.myConfig = { debug: false };
if (typeof globalThis.fetch === 'function') {
// native fetch available
}
// globalThis === window (browser)
// globalThis === global (Node.js)
// globalThis === self (Web Worker)
Why it matters: globalThis enables truly isomorphic code — libraries that run in browsers, Node.js, Deno, and Web Workers without environment checks. It's the standard way to write environment-agnostic feature detection and polyfill injection code.
Real applications: Polyfill libraries inject missing features (like Promise, fetch) into globalThis to work universally. Build tools like Webpack and esbuild use globalThis to implement module-level globals in bundled output that runs across environments.
Common mistakes: Mutating globalThis to share values between modules (use proper module exports instead — global state causes testing nightmares), and assuming globalThis === this at the top level of a Node.js CJS module (top-level this is module.exports, not the global).
get/set keywords, this follows the same rules as regular methods — it refers to the object that owns the property at the call site. Despite their property-access syntax (no parentheses), getters and setters behave like regular method calls for this binding.
const user = {
firstName: 'Alice',
lastName: 'Smith',
get fullName() {
return this.firstName + ' ' + this.lastName; // this = user
},
set fullName(value) {
const [first, last] = value.split(' ');
this.firstName = first;
this.lastName = last;
}
};
console.log(user.fullName); // "Alice Smith"
user.fullName = 'Bob Jones';
console.log(user.firstName); // "Bob"
class Circle {
#radius;
constructor(r) { this.#radius = r; }
get area() { return Math.PI * this.#radius ** 2; }
set radius(r) {
if (r < 0) throw new Error('Negative radius');
this.#radius = r;
}
}
Why it matters: Getters and setters are the standard pattern for computed properties and validation in JavaScript classes. Understanding their this behavior lets you use private fields, perform lazy initialization, and add change validation while keeping clean property-access syntax.
Real applications: Vue 2's reactive system used getters and setters (via Object.defineProperty) to intercept property reads/writes and trigger dependency tracking and re-rendering. Understanding this in getters was central to Vue 2's internals.
Common mistakes: Trying to define a getter as an arrow function (syntax error — get/set keywords require method syntax), and creating infinite recursion by assigning to the same property name inside a setter without using a backing variable or private field.
this inside the method refers to the Proxy object, not the original target. This usually works fine for regular properties but causes failures for private class fields and built-in objects with internal slots (Map, Set, Date) that check the actual object identity, not the proxy.
const target = { name: 'Alice', greet() { return 'Hi, ' + this.name; } };
const proxy = new Proxy(target, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
}
});
proxy.greet(); // "Hi, Alice" — this = proxy, name lookup works
// Problem: private fields fail through proxy
class Secret {
#value = 42;
getValue() { return this.#value; }
}
const s = new Secret();
const sProxy = new Proxy(s, {});
// sProxy.getValue(); // TypeError: cannot read private member
// Fix: bind methods to original target in get trap
const fixed = new Proxy(s, {
get(target, prop) {
const val = target[prop];
return typeof val === 'function' ? val.bind(target) : val;
}
});
Why it matters: Proxy-based reactivity systems (Vue 3, MobX) must handle the proxy/this issue carefully. Understanding this limitation helps library authors write correct proxy wrappers and helps consumers know when a proxy-wrapped object might behave unexpectedly.
Real applications: Vue 3's reactive system uses Proxy to intercept all property accesses. It handles the this-through-proxy issue by passing receiver through Reflect.get(target, prop, receiver), which correctly propagates this for prototype chains.
Common mistakes: Proxying built-in objects like Map or Date without binding methods — their internal operations check that this is the actual built-in instance, not a proxy, so methods throw "incompatible receiver" TypeErrors without the binding fix.
this follows a priority-based decision tree: check the four binding rules in order — the first match wins. Arrow functions are a special case outside the priority system — they always use the lexical (enclosing scope) this and never participate in the normal binding hierarchy.
// Priority order — check top to bottom:
// 1. new keyword — this = newly created object
function Foo() { console.log(this); }
new Foo(); // {}
// 2. Explicit binding (call/apply/bind) — this = specified
foo.call({ x: 1 }); // { x: 1 }
// 3. Implicit binding (method call) — this = object before dot
const obj = { fn() { console.log(this); } };
obj.fn(); // obj
// 4. Default — this = global (sloppy) or undefined (strict)
function baz() { console.log(this); }
baz(); // window or undefined
// Special: Arrow function — IGNORES all rules above
const fn = () => this; // always captures outer 'this' at definition
// Quick cheat sheet:
// new? → new object
// call/apply/bind? → specified object
// obj.method()? → obj
// standalone? → global/undefined
// arrow function? → lexical this (outer scope)
Why it matters: This priority framework makes this completely predictable in any scenario. Instead of treating this as magic or guessing, you apply the 4-rule hierarchy mechanically. This is the mental model interviewers want to see when they ask "what is this here?"
Real applications: Code reviews and debugging use this mental model to instantly identify why a method call has the wrong this. When frameworks document "lifecycle hooks receive the component instance as this", they're specifying which binding rule applies (implicit, via internal call to hook.call(instance)).
Common mistakes: Including arrow functions in the priority system instead of treating them as a separate special case (arrow this is always lexical, regardless of how they're called), and forgetting that a bind()-bound function's this is permanent — subsequent call()/apply()/bind() calls are silently ignored.