Back to Blog
JavaScriptthisFunctionsFundamentals

JavaScript this Keyword Guide

Master the JavaScript this keyword and understand its behavior in different contexts.

B
Bootspring Team
Engineering
September 17, 2018
6 min read

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(); // 27

Borrowing 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); // 15

Implicit 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 this

Common 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.

Share this article

Help spread the word about Bootspring