|$ curl https://forge-ai.dev/api/markdown?path=docs/js/this-keyword
$cat docs/javascript-—-the-`this`-keyword.md
updated Recently·30 min read·published

JavaScript — The `this` Keyword

JavaScriptthisIntermediateIntermediate
Introduction

The this keyword is one of the most misunderstood concepts in JavaScript. Unlike other languages where this always refers to the current instance of a class, in JavaScript the value of this is determined by how a function is called, not where it is defined.

There are five binding rules that determine the value of this: default binding, implicit binding, explicit binding, new binding, and lexical binding with arrow functions. Understanding these rules and their precedence is essential for writing correct JavaScript.

Each call site — the location in code where a function is invoked — determines what this will be. The same function can have different this values depending on how it is called. This dynamic nature is powerful but requires careful attention.

Binding RuleHow this is DeterminedExample Call
DefaultGlobal object (window) or undefined in strict modefoo()
ImplicitThe object before the dotobj.foo()
ExplicitValue passed to call/apply/bindfoo.call(obj)
newThe newly created instancenew Foo()
Lexical (Arrow)this from the enclosing scope() => this.x
Default Binding

Default binding applies when a function is called as a standalone function — no object, no new, no explicit binding. In non-strict mode, this defaults to the global object (window in browsers, global in Node.js). In strict mode, this is undefined.

default-binding.js
JavaScript
1// Non-strict mode — default binding binds to global object
2function showThis() {
3 console.log(this); // window (in browser)
4}
5showThis();
6
7// Strict mode — default binding is undefined
8function strictShow() {
9 "use strict";
10 console.log(this); // undefined
11}
12strictShow();
13
14// Common pitfall: method extracted from its object
15const user = {
16 name: "Alice",
17 greet() {
18 console.log(`Hello, I'm ${this.name}`);
19 }
20};
21
22const greetFn = user.greet; // Extract method reference
23greetFn(); // Default binding applies — this is undefined or window
24// "Hello, I'm undefined" (strict) or "Hello, I'm " (non-strict)
25
26// Same issue with passing methods as callbacks
27setTimeout(user.greet, 100); // this lost!
28// Fix: wrap in arrow function or use bind
29setTimeout(() => user.greet(), 100); // works
30setTimeout(user.greet.bind(user), 100); // also works
31
32// Nested function — inner function gets default binding
33const counter = {
34 count: 0,
35 start() {
36 function increment() {
37 this.count++; // this is window or undefined, NOT counter
38 }
39 increment(); // default binding!
40 }
41};
42// Fix: capture this, use arrow, or bind

warning

The most common this bug is method extraction — taking a method from an object and calling it without the object reference. This happens frequently when passing methods as callbacks to setTimeout, event listeners, or array methods. Always ensure this is correctly bound when passing methods around.
Implicit Binding

When a function is called as a method of an object (using the dot notation or bracket notation), this is bound to the object that owns the method call. The rule is simple: look at the left side of the dot at the call site.

implicit-binding.js
JavaScript
1// Implicit binding — this is the object before the dot
2const person = {
3 name: "Bob",
4 greet() {
5 console.log(`Hi, I'm ${this.name}`);
6 }
7};
8person.greet(); // "Hi, I'm Bob" — this is person
9
10// Bracket notation also uses implicit binding
11const method = "greet";
12person[method](); // "Hi, I'm Bob" — same rule
13
14// Chained property access — only the last level matters
15const obj1 = {
16 name: "obj1",
17 child: {
18 name: "obj2",
19 greet() {
20 console.log(this.name);
21 }
22 }
23};
24obj1.child.greet(); // "obj2" — this is obj1.child (the last object)
25
26// Implicit binding is lost in indirection
27const greet = person.greet;
28greet(); // default binding! this is not person
29
30// Implicitly bound functions passed as arguments lose this
31function callTwice(fn) {
32 fn();
33 fn();
34}
35callTwice(person.greet); // default binding both times
36
37// Context loss with object destructuring
38const { greet } = person;
39greet(); // default binding — this is not person
Call Patternthis ValueExample
Standalone callDefault bindingfoo()
Method call (dot)Object before dotobj.foo()
Method referenceDefault binding (lost!)const f = obj.foo; f()
Callback passedDefault binding (lost!)setTimeout(obj.foo, 100)
Explicit Binding

JavaScript gives you direct control over this via call, apply, and bind. These methods allow you to explicitly set the this value for a function invocation, overriding whatever implicit or default binding would have applied.

call and apply

call and apply invoke the function immediately with a specified this. The only difference is how arguments are passed: call takes comma-separated arguments, apply takes an array of arguments.

call-apply.js
JavaScript
1// call — explicit this + comma-separated arguments
2function introduce(greeting, punctuation) {
3 console.log(`${greeting}, I'm ${this.name}${punctuation}`);
4}
5
6const alice = { name: "Alice" };
7const bob = { name: "Bob" };
8
9introduce.call(alice, "Hello", "!"); // "Hello, I'm Alice!"
10introduce.call(bob, "Hey", "."); // "Hey, I'm Bob."
11
12// apply — same but arguments as array
13introduce.apply(alice, ["Hi", "!!!"]); // "Hi, I'm Alice!!!"
14
15// Practical: Math.max with an array
16const numbers = [3, 7, 1, 9, 4];
17console.log(Math.max.apply(null, numbers)); // 9
18// Modern: Math.max(...numbers)
19
20// Practical: array-like to array conversion (pre-ES6)
21function argsToArray() {
22 return Array.prototype.slice.apply(arguments);
23 // or: Array.from(arguments)
24}
25
26// First argument to call/apply sets this
27// If null/undefined passed, default binding applies (global or undefined in strict)

bind

Unlike call and apply, bind does not invoke the function. Instead, it creates a new function with the this value permanently bound. Once a function is bound, its this cannot be overridden — even by call or apply.

bind-examples.js
JavaScript
1// bind — creates a new function with fixed this
2function greet() {
3 console.log(`Hello, ${this.name}`);
4}
5
6const user = { name: "Charlie" };
7const boundGreet = greet.bind(user);
8boundGreet(); // "Hello, Charlie"
9
10// Partial application — bind also pre-fills arguments
11function multiply(a, b) {
12 return a * b;
13}
14const double = multiply.bind(null, 2); // permanently set first arg to 2
15console.log(double(5)); // 10 (2 * 5)
16const triple = multiply.bind(null, 3);
17console.log(triple(5)); // 15
18
19// Bound this cannot be overridden
20const other = { name: "Diana" };
21boundGreet.call(other); // Still "Hello, Charlie" — bind is permanent
22boundGreet.apply(other); // Still "Hello, Charlie"
23
24// Practical: fixing this in callbacks
25const button = {
26 text: "Click me",
27 listeners: [],
28 addListener(fn) {
29 this.listeners.push(fn);
30 },
31 onClick() {
32 console.log(`Button "${this.text}" clicked`);
33 }
34};
35
36// Without bind — this is lost
37const onClick = button.onClick;
38// onClick(); // this is undefined or window
39
40// With bind — this is preserved
41const boundOnClick = button.onClick.bind(button);
42boundOnClick(); // "Button "Click me" clicked"
43
44// bind in event handlers
45element.addEventListener("click", button.onClick.bind(button));
🔥

pro tip

bind returns a new function every time it is called. If you use bind inside a render or setup method that runs multiple times, you create a new function each time — this matters for removeEventListener and React re-renders. Store the bound function in a variable or use a class property arrow function instead.
Arrow Function Lexical `this`

Arrow functions do not have their own this. Instead, they inherit this from the enclosing lexical scope — the surrounding non-arrow function at the time the arrow function is defined. This is called lexical binding. Arrow functions cannot be used with new, and call/apply/bind cannot change their this.

arrow-function-this.js
JavaScript
1// Arrow functions inherit this from enclosing scope
2const obj = {
3 name: "Object",
4 regular: function() {
5 console.log("regular:", this.name); // this is obj
6
7 const arrow = () => {
8 console.log("arrow:", this.name); // this is also obj (inherited)
9 };
10 arrow();
11 },
12 arrowMethod: () => {
13 console.log("arrowMethod:", this.name); // this is NOT obj!
14 // Arrow methods on objects use enclosing scope (global/window)
15 }
16};
17
18obj.regular(); // regular: Object / arrow: Object
19obj.arrowMethod(); // arrowMethod: undefined (wrong!)
20
21// Arrow functions fix the "lost this" in nested functions
22const timer = {
23 count: 0,
24 start() {
25 // Old way — capture this
26 const self = this;
27 setInterval(function() {
28 self.count++;
29 }, 1000);
30
31 // Better — arrow function inherits this from start()
32 setInterval(() => {
33 this.count++; // this is timer (inherited from start)
34 }, 1000);
35 }
36};
37
38// Arrow functions in array methods preserve this
39const team = {
40 name: "Dev Team",
41 members: ["Alice", "Bob"],
42 printMembers() {
43 this.members.forEach((member) => {
44 // Arrow inherits this from printMembers
45 console.log(`${this.name}: ${member}`);
46 });
47 }
48};
49team.printMembers(); // "Dev Team: Alice", "Dev Team: Bob"

warning

Do not use arrow functions for object methods that need this to refer to the object. Arrow functions on objects — like const obj = { method: () => { ... } } — inherit this from the outer scope, which is typically the global object or undefined. Use regular function syntax for object methods and arrow functions for callbacks and nested functions.
new Binding

When a function is invoked with the new keyword, a new object is created, and this is bound to that new object. The new object is returned automatically unless the constructor returns a non-primitive object.

new-binding.js
JavaScript
1// new binding — this is the new instance
2function Person(name) {
3 // this = newly created object
4 this.name = name;
5 this.greet = function() {
6 console.log(`Hi, I'm ${this.name}`);
7 };
8 // return this; (implicit)
9}
10
11const alice = new Person("Alice");
12alice.greet(); // "Hi, I'm Alice"
13console.log(alice instanceof Person); // true
14
15// new binding takes precedence over implicit binding
16function Dog(name) {
17 this.name = name;
18}
19
20const dog1 = new Dog("Rex");
21const obj = { Dog: Dog };
22const dog2 = new obj.Dog("Spot"); // new binding wins over implicit
23console.log(dog2.name); // "Spot"
24
25// new binding also beats explicit binding (bind)
26function Foo() {
27 console.log(this instanceof Foo); // true when called with new
28}
29const boundFoo = Foo.bind({});
30new boundFoo(); // this is a new Foo instance, NOT the bound object
31// new binding: highest precedence (after arrow)
32
33// Constructor return value:
34// If constructor returns an object, that object is used instead of this
35function ReturnsObject() {
36 this.name = "default";
37 return { name: "override" };
38}
39const instance = new ReturnsObject();
40console.log(instance.name); // "override" — returned object wins
41
42// If constructor returns a primitive, it's ignored
43function ReturnsPrimitive() {
44 this.name = "real";
45 return 42; // primitive — ignored
46}
47const instance2 = new ReturnsPrimitive();
48console.log(instance2.name); // "real"
RulePrecedenceCan be Overridden?
Arrow functionHighest (cannot change)No
newSecondNo (if bind applied before new, new wins)
Explicit (call/apply/bind)ThirdBeats implicit, not new or arrow
Implicit (method call)FourthBeats default
DefaultLowestAlways overridden by any other rule
Event Handler `this`

In DOM event handlers, this behaves differently depending on how the handler is registered. With addEventListener, this refers to the element the listener is attached to (currentTarget). Inline handlers and arrow functions have their own behaviors.

event-handler-this.js
JavaScript
1// addEventListener — this is the element
2const button = document.getElementById("myButton");
3button.addEventListener("click", function() {
4 console.log(this); // <button> element
5 console.log(this.id); // "myButton"
6 console.log(this === e.currentTarget); // true
7});
8
9// Arrow function in addEventListener — this is lexical
10button.addEventListener("click", (e) => {
11 console.log(this); // NOT the button! (window or undefined)
12 // Use e.currentTarget instead
13 console.log(e.currentTarget.id); // "myButton"
14});
15
16// Inline handler — this is the element
17// <button onclick="console.log(this)">Click</button>
18// this is the <button> element
19
20// Inline handler with strict module
21// <button onclick="handleClick()">Click</button>
22// handleClick() gets default binding — this is undefined in strict mode
23
24// Class method as event handler — this gets lost!
25class MyComponent {
26 constructor(element) {
27 this.element = element;
28 this.count = 0;
29
30 // BAD — this will be the element when called
31 element.addEventListener("click", this.handleClick);
32
33 // GOOD — bound
34 element.addEventListener("click", this.handleClick.bind(this));
35
36 // GOOD — arrow function wrapper
37 element.addEventListener("click", (e) => this.handleClick(e));
38
39 // BEST — class property arrow (see bind vs arrow section)
40 }
41
42 handleClick() {
43 this.count++;
44 console.log(`Clicked ${this.count} times`);
45 }
46}

info

When using addEventListener with a regular function, this is set to the element — this is actually useful for reusable handlers. But arrow functions are often preferred for their predictable lexical this, and you access the element via e.currentTarget instead.
bind vs Arrow in Classes

In JavaScript classes, method this is lost when the method is extracted and used as a callback. Two common solutions exist: binding in the constructor or using class property arrow functions. Each has tradeoffs in terms of performance, inheritance, and testability.

bind-vs-arrow.js
JavaScript
1// Approach 1: bind in constructor
2class Toggle1 {
3 constructor() {
4 this.active = false;
5 this.handleClick = this.handleClick.bind(this);
6 }
7
8 handleClick() {
9 this.active = !this.active;
10 console.log(`Active: ${this.active}`);
11 }
12}
13
14// Approach 2: Class property arrow function
15class Toggle2 {
16 active = false;
17
18 handleClick = () => {
19 this.active = !this.active;
20 console.log(`Active: ${this.active}`);
21 };
22}
23
24// Approach 3: Arrow wrapper (creates new function each render)
25class Toggle3 {
26 active = false;
27
28 handleClick() {
29 this.active = !this.active;
30 }
31
32 render() {
33 // BAD — new function every render
34 return <button onClick={() => this.handleClick()} />;
35
36 // BETTER — stable reference
37 // return <button onClick={this.handleClick} />;
38 }
39}
40
41// Tradeoffs:
42// bind — method on prototype, one function per instance, works with super
43// arrow class property — NOT on prototype, one function per instance, no super.method()
44
45// Arrow class property — no prototype method
46class Parent {
47 greet = () => console.log("Parent");
48}
49class Child extends Parent {
50 greet = () => {
51 // super.greet(); // ERROR — greet is not on prototype
52 console.log("Child");
53 };
54}
55
56// bind — works with prototype chain
57class Parent2 {
58 constructor() {
59 this.greet = this.greet.bind(this);
60 }
61 greet() { console.log("Parent"); }
62}
63class Child2 extends Parent2 {
64 greet() {
65 super.greet(); // works!
66 console.log("Child");
67 }
68}
Aspectbind in constructorClass property arrow
PrototypeMethod on prototypeOwn property, not on prototype
super.method()SupportedNot supported
Mock in testsEasy (prototype)Per-instance
MemoryOne bound copy per instanceOne arrow per instance
Common this Pitfalls

1. Losing this in Nested Functions

pitfall-nested.js
JavaScript
1function Outer() {
2 this.value = 42;
3
4 function inner() {
5 console.log(this.value); // undefined — this is global/undefined
6 }
7 inner();
8}
9new Outer(); // undefined
10
11// Fix: capture this in a variable
12function OuterFixed() {
13 this.value = 42;
14 const self = this;
15
16 function inner() {
17 console.log(self.value); // 42
18 }
19 inner();
20}
21
22// Fix: use arrow function
23function OuterFixed2() {
24 this.value = 42;
25 const inner = () => {
26 console.log(this.value); // 42
27 };
28 inner();
29}

2. this in setTimeout/setInterval

pitfall-timer.js
JavaScript
1function Timer() {
2 this.seconds = 0;
3
4 // BAD — this is the timer object itself... no wait
5 setInterval(function() {
6 this.seconds++; // this is window/global, not Timer!
7 }, 1000);
8
9 // FIX 1: capture this
10 const self = this;
11 setInterval(function() {
12 self.seconds++;
13 }, 1000);
14
15 // FIX 2: bind
16 setInterval(function() {
17 this.seconds++;
18 }.bind(this), 1000);
19
20 // FIX 3: arrow function
21 setInterval(() => {
22 this.seconds++;
23 }, 1000);
24}

3. this in Array Methods

pitfall-array.js
JavaScript
1const data = {
2 values: [1, 2, 3],
3 multiplier: 2,
4 multiply() {
5 // BAD — function callback loses this
6 return this.values.map(function(v) {
7 return v * this.multiplier; // this is undefined/window
8 });
9 },
10 multiplyFixed() {
11 // GOOD — arrow preserves this
12 return this.values.map((v) => v * this.multiplier);
13 }
14};
15
16// 4. this with destructuring
17function greet() {
18 console.log(`Hello, ${this.name}`);
19}
20const { name } = { name: "Test" };
21greet(); // this is not the destructured object
22
23// 5. Strict vs non-strict mode
24// In modules (ESM), strict mode is always on
25// All class bodies are strict by default
26// "use strict" at the top of a file enables it globally for that file
27
28// 6. this in eval (avoid eval)
29function evalTest() {
30 const x = eval("this");
31 console.log(x); // depends on strict mode context
32}

Quick Reference — How to Fix this:

Use arrow functions for callbacks — they inherit this from the enclosing scope
Use .bind(this) when you need a stable reference for a regular function
Use class property arrow functions (handleClick = () => { ... }) for React/class methods
Store this in a variable (const self = this) in older code patterns
Use event.currentTarget instead of this in DOM event handlers with arrow functions
Never use arrow functions for object methods that need dynamic this
Always check strict mode — it changes default binding from global to undefined

danger

The most common this bug in modern JavaScript: using this inside a regular (non-arrow) function callback, expecting it to inherit this from the enclosing scope. Remember: only arrow functions have lexical this. Regular functions determine this at call time based on the call site.
$Blueprint — Engineering Documentation·Section ID: JS-THIS·Revision: 1.0