The this keyword refers to the execution context. Here's how it works in different scenarios.
Global Context#
1// In global scope (non-strict mode)
2console.log(this); // Window (browser) or global (Node.js)
3
4// In strict mode
5'use strict';
6console.log(this); // undefined
7
8// Global function
9function showThis() {
10 console.log(this);
11}
12
13showThis(); // Window (non-strict) or undefined (strict)Object Methods#
1// this refers to the object calling the method
2const user = {
3 name: 'John',
4 greet() {
5 console.log(`Hello, ${this.name}`);
6 }
7};
8
9user.greet(); // 'Hello, John'
10
11// Method assignment loses context
12const greet = user.greet;
13greet(); // 'Hello, undefined' (this is global/undefined)
14
15// Nested objects
16const company = {
17 name: 'Acme',
18 department: {
19 name: 'Engineering',
20 getName() {
21 return this.name; // 'Engineering', not 'Acme'
22 }
23 }
24};Arrow Functions#
1// Arrow functions inherit this from enclosing scope
2const user = {
3 name: 'John',
4 greet: () => {
5 console.log(this.name); // undefined (inherits global this)
6 }
7};
8
9// Useful in callbacks
10const user2 = {
11 name: 'Jane',
12 friends: ['Alice', 'Bob'],
13 showFriends() {
14 this.friends.forEach((friend) => {
15 // Arrow function inherits this from showFriends
16 console.log(`${this.name} knows ${friend}`);
17 });
18 }
19};
20
21user2.showFriends();
22// 'Jane knows Alice'
23// 'Jane knows Bob'
24
25// Compare with regular function
26const user3 = {
27 name: 'Jim',
28 friends: ['Carol'],
29 showFriends() {
30 this.friends.forEach(function(friend) {
31 console.log(`${this.name} knows ${friend}`); // undefined!
32 });
33 }
34};Explicit Binding#
1// call() - invoke with specific this
2function greet(greeting) {
3 console.log(`${greeting}, ${this.name}`);
4}
5
6const user = { name: 'John' };
7greet.call(user, 'Hello'); // 'Hello, John'
8
9// apply() - like call but with array arguments
10greet.apply(user, ['Hi']); // 'Hi, John'
11
12// bind() - create new function with bound this
13const boundGreet = greet.bind(user);
14boundGreet('Hey'); // 'Hey, John'
15
16// Bind with preset arguments
17const sayHelloToJohn = greet.bind(user, 'Hello');
18sayHelloToJohn(); // 'Hello, John'
19
20// Bound functions can't be rebound
21const newUser = { name: 'Jane' };
22boundGreet.call(newUser, 'Hi'); // 'Hi, John' (still John!)Constructor Functions#
1// this refers to the new instance
2function Person(name, age) {
3 this.name = name;
4 this.age = age;
5 this.greet = function() {
6 console.log(`I'm ${this.name}`);
7 };
8}
9
10const john = new Person('John', 30);
11john.greet(); // "I'm John"
12
13// Without new, this is global (dangerous!)
14const jane = Person('Jane', 25); // Returns undefined
15console.log(window.name); // 'Jane' (leaked to global!)
16
17// ES6 classes
18class User {
19 constructor(name) {
20 this.name = name;
21 }
22
23 greet() {
24 console.log(`Hello, ${this.name}`);
25 }
26}
27
28const user = new User('John');
29user.greet(); // 'Hello, John'Event Handlers#
1// DOM event handlers - this is the element
2button.addEventListener('click', function() {
3 console.log(this); // <button> element
4 this.classList.toggle('active');
5});
6
7// Arrow function - this is enclosing scope
8button.addEventListener('click', () => {
9 console.log(this); // Window or enclosing this
10});
11
12// Class method as handler
13class Button {
14 constructor(element) {
15 this.element = element;
16 this.count = 0;
17
18 // Problem: loses this
19 element.addEventListener('click', this.handleClick);
20
21 // Solution 1: bind
22 element.addEventListener('click', this.handleClick.bind(this));
23
24 // Solution 2: arrow function
25 element.addEventListener('click', () => this.handleClick());
26
27 // Solution 3: arrow method
28 element.addEventListener('click', this.handleClickArrow);
29 }
30
31 handleClick() {
32 this.count++; // Works only with binding
33 }
34
35 handleClickArrow = () => {
36 this.count++; // Always works (arrow property)
37 }
38}Callback Context#
1// setTimeout loses context
2const user = {
3 name: 'John',
4 greetLater() {
5 setTimeout(function() {
6 console.log(`Hello, ${this.name}`); // undefined
7 }, 1000);
8 }
9};
10
11// Solutions:
12
13// 1. Arrow function
14const user1 = {
15 name: 'John',
16 greetLater() {
17 setTimeout(() => {
18 console.log(`Hello, ${this.name}`); // 'Hello, John'
19 }, 1000);
20 }
21};
22
23// 2. Save reference
24const user2 = {
25 name: 'John',
26 greetLater() {
27 const self = this;
28 setTimeout(function() {
29 console.log(`Hello, ${self.name}`); // 'Hello, John'
30 }, 1000);
31 }
32};
33
34// 3. Bind
35const user3 = {
36 name: 'John',
37 greetLater() {
38 setTimeout(function() {
39 console.log(`Hello, ${this.name}`);
40 }.bind(this), 1000);
41 }
42};Method Chaining#
1// Return this for chaining
2class Calculator {
3 constructor(value = 0) {
4 this.value = value;
5 }
6
7 add(n) {
8 this.value += n;
9 return this;
10 }
11
12 subtract(n) {
13 this.value -= n;
14 return this;
15 }
16
17 multiply(n) {
18 this.value *= n;
19 return this;
20 }
21
22 getResult() {
23 return this.value;
24 }
25}
26
27const result = new Calculator(10)
28 .add(5)
29 .multiply(2)
30 .subtract(3)
31 .getResult(); // 27Borrowing Methods#
1// Use methods from other objects
2const arrayLike = {
3 0: 'a',
4 1: 'b',
5 2: 'c',
6 length: 3
7};
8
9// Borrow Array methods
10const arr = Array.prototype.slice.call(arrayLike);
11// ['a', 'b', 'c']
12
13// Or with spread (modern)
14const arr2 = [...arrayLike];
15
16// Borrow specific methods
17const numbers = {
18 values: [1, 2, 3],
19 sum() {
20 return this.values.reduce((a, b) => a + b, 0);
21 }
22};
23
24const otherNumbers = {
25 values: [4, 5, 6]
26};
27
28// Borrow sum method
29numbers.sum.call(otherNumbers); // 15Implicit vs Explicit this#
1// Implicit binding rules (in order of precedence):
2
3// 1. new binding (constructor)
4function Foo() {
5 this.value = 42;
6}
7const foo = new Foo(); // this = new object
8
9// 2. Explicit binding (call/apply/bind)
10foo.method.call(obj); // this = obj
11
12// 3. Implicit binding (object method)
13obj.method(); // this = obj
14
15// 4. Default binding (standalone function)
16method(); // this = global or undefined
17
18// Arrow functions ignore these rules
19// They always use lexical thisCommon Patterns#
1// Private data with closures (avoiding this)
2function createCounter() {
3 let count = 0;
4 return {
5 increment() { count++; },
6 getCount() { return count; }
7 };
8}
9
10// Functional approach (no this)
11const userFunctions = (user) => ({
12 greet: () => `Hello, ${user.name}`,
13 getAge: () => user.age
14});
15
16// Class with bound methods
17class Service {
18 constructor() {
19 this.handleRequest = this.handleRequest.bind(this);
20 }
21
22 handleRequest(req) {
23 // this is always the instance
24 }
25}
26
27// Class with arrow properties
28class ModernService {
29 handleRequest = (req) => {
30 // this is always the instance
31 }
32}Best Practices#
Understanding this:
✓ Know the binding rules
✓ Understand arrow functions
✓ Use explicit binding when needed
✓ Be aware of callback context
Arrow Functions:
✓ Use in callbacks/closures
✓ Use as class properties
✓ Don't use as object methods
✓ Don't use when this needed
Classes:
✓ Bind methods in constructor
✓ Or use arrow properties
✓ Be consistent in approach
✓ Return this for chaining
Avoid:
✗ Relying on implicit binding
✗ Forgetting to bind handlers
✗ Mixing arrow and regular methods
✗ Using this in nested functions
Conclusion#
The this keyword is context-dependent: global context, object methods, constructors, or explicit binding with call/apply/bind all affect its value. Arrow functions inherit this from their enclosing scope, making them ideal for callbacks. Always be aware of how your function is called, and use explicit binding when context might be lost.