JavaScript and TypeScript course ¡ Module 2: Functions, Arrays and Objects

The this Keyword in JavaScript

14 min read
In this lesson9

Welcome back to Jurassic Park! Today we will dive deep into one of the most intriguing and sometimes confusing topics in JavaScript: the this keyword. Understanding this is crucial for effective object-oriented programming in JavaScript and for building complex systems like our park management system.

What is this?

The this keyword in JavaScript refers to the object in whose context a function is being executed. You can think of it as the "current workplace" - just like a Jurassic Park employee might work in different locations (e.g., the lab, the T-Rex enclosure, the control center), a function can be executed in different contexts.

The value of this is not determined when the function is defined, but when it is called, which can cause some confusion. The value of this depends on how the function is invoked:

  1. In the global context
  2. As an object method
  3. As a constructor
  4. Using the call(), apply(), or bind() methods
  5. In arrow functions

Let's look at each of these cases.

1. this in Global Context

When this is used in a regular function (not called as an object method, outside strict mode), it refers to the global object: window in the browser and global in Node.js (globalThis is the common name for both). At the top level of a browser script this is also window, but in a Node.js file it is module.exports, an empty object {}, and in ES modules it is undefined.

1// In the global scope
2console.log(this); // window in the browser, {} (module.exports) in a Node.js file
3
4// In a regular function
5function checkGlobalThis() {
6  console.log(this); // also window/global object
7}
8
9checkGlobalThis();

In strict mode ("use strict";), this in a regular function will be undefined instead of the global object, which helps avoid accidentally creating global variables.

1"use strict";
2
3function strictThis() {
4  console.log(this); // undefined
5}
6
7strictThis();

2. this as an Object Method

When a function is called as a method of an object, this refers to that object. This is one of the most common and intuitive uses of this in JavaScript.

1// Object representing a dinosaur
2const velociraptor = {
3  name: "Blue",
4  species: "Velociraptor",
5  age: 5,
6
7  // Method using this
8  describe: function() {
9    console.log(`This ${this.species} is named ${this.name} and is ${this.age} years old.`);
10  },
11
12  // Method that modifies dinosaur data
13  ageDinosaur: function(years) {
14    this.age += years;
15    console.log(`${this.name} is now ${this.age} years old.`);
16  }
17};
18
19// Calling methods
20velociraptor.describe(); // "This Velociraptor is named Blue and is 5 years old."
21velociraptor.ageDinosaur(2); // "Blue is now 7 years old."

In the example above, this inside the describe and ageDinosaur methods refers to the velociraptor object, allowing the methods to access its properties.

Potential Pitfall: Losing Context

However, be careful - the this context can be lost if you assign a method to another variable and call it as a regular function:

1const velociraptor = {
2  name: "Blue",
3  describe: function() {
4    console.log(`Hi, I'm ${this.name}!`);
5  }
6};
7
8// Calling as a method - this refers to velociraptor
9velociraptor.describe(); // "Hi, I'm Blue!"
10
11// But when we assign the method to a variable...
12const describeDino = velociraptor.describe;
13
14// ...and call it as a regular function
15describeDino(); // "Hi, I'm undefined!"
16// (or "Hi, I'm !" if a global name variable exists)

This is a common problem when passing methods as callbacks:

1const trex = {
2  name: "Rex",
3  roar: function() {
4    console.log(`${this.name} roars loudly!`);
5  }
6};
7
8// Direct call works
9trex.roar(); // "Rex roars loudly!"
10
11// But as a callback after 1 second...
12setTimeout(trex.roar, 1000); // this does not point to trex, so the name Rex will not appear
13
14// Solution (one way) is to use an arrow function or bind():
15setTimeout(() => trex.roar(), 1000); // "Rex roars loudly!"
16setTimeout(trex.roar.bind(trex), 1000); // "Rex roars loudly!"

3. this in Constructors

When a function is called with the new operator (as a constructor), this refers to the newly created object. This is a fundamental mechanism for creating object instances in JavaScript.

1// Constructor function for dinosaurs
2function Dinosaur(name, species, age) {
3  // 'this' refers to the newly created object
4  this.name = name;
5  this.species = species;
6  this.age = age;
7
8  this.describe = function() {
9    console.log(`${this.name} is a ${this.species} aged ${this.age} years.`);
10  };
11}
12
13// Creating new instances
14const blue = new Dinosaur("Blue", "Velociraptor", 5);
15const rex = new Dinosaur("Rex", "Tyrannosaurus", 8);
16
17blue.describe(); // "Blue is a Velociraptor aged 5 years."
18rex.describe(); // "Rex is a Tyrannosaurus aged 8 years."
19
20// Let's modify Blue's age
21blue.age = 6;
22blue.describe(); // "Blue is a Velociraptor aged 6 years."

In modern JavaScript (ES6+), instead of constructor functions we often use classes, which provide a cleaner syntax for doing the same thing:

1// ES6 class for dinosaurs
2class Dinosaur {
3  constructor(name, species, age) {
4    this.name = name;
5    this.species = species;
6    this.age = age;
7  }
8
9  // Methods are automatically added to the prototype
10  describe() {
11    console.log(`${this.name} is a ${this.species} aged ${this.age} years.`);
12  }
13
14  // Method that changes the age
15  growOlder(years = 1) {
16    this.age += years;
17    console.log(`${this.name} is now ${this.age} years old.`);
18  }
19}
20
21// Creating instances
22const trixie = new Dinosaur("Trixie", "Triceratops", 7);
23trixie.describe(); // "Trixie is a Triceratops aged 7 years."
24trixie.growOlder(3); // "Trixie is now 10 years old."

4. Controlling this: call(), apply(), and bind()

JavaScript offers three methods that allow you to explicitly control the value of this when calling a function:

call()

The call() method calls a function with a specified this value and arguments passed individually:

1function examineSpecimen(testType, labNumber) {
2  console.log(`Examining ${this.species} named ${this.name}...`);
3  console.log(`Test type: ${testType}, Laboratory: ${labNumber}`);
4}
5
6const specimen = {
7  name: "Echo",
8  species: "Velociraptor",
9  age: 3
10};
11
12// Calling the function with 'this' set to specimen
13examineSpecimen.call(specimen, "DNA", "Lab-A");
14// "Examining Velociraptor named Echo..."
15// "Test type: DNA, Laboratory: Lab-A"

apply()

The apply() method works similarly to call(), but arguments are passed as an array:

1// Same function
2function examineSpecimen(testType, labNumber) {
3  console.log(`Examining ${this.species} named ${this.name}...`);
4  console.log(`Test type: ${testType}, Laboratory: ${labNumber}`);
5}
6
7// Calling with arguments in an array
8examineSpecimen.apply(specimen, ["Behavioral", "Lab-B"]);
9// "Examining Velociraptor named Echo..."
10// "Test type: Behavioral, Laboratory: Lab-B"

bind()

The bind() method creates a new function with a permanently assigned this value:

1const dinosaur = {
2  name: "Delta",
3  species: "Velociraptor"
4};
5
6function makeSound(sound) {
7  console.log(`${this.name} makes a sound: ${sound}!`);
8}
9
10// Creating a new function with 'this' bound to dinosaur
11const dinosaurSound = makeSound.bind(dinosaur);
12
13// Now we can call this function multiple times
14dinosaurSound("Growling"); // "Delta makes a sound: Growling!"
15dinosaurSound("Hissing"); // "Delta makes a sound: Hissing!"
16
17// We can also pre-bind arguments
18const deltaRoar = makeSound.bind(dinosaur, "ROAR");
19deltaRoar(); // "Delta makes a sound: ROAR!"

Practical Example: Dinosaur Tracking System

The following example shows how different methods of controlling this can be used to create a flexible monitoring system:

1const dinoMonitoringSystem = {
2  activeDinosaurs: [],
3
4  // Register a new dinosaur
5  registerDinosaur: function(dinosaur) {
6    this.activeDinosaurs.push(dinosaur);
7    console.log(`Registered new dinosaur: ${dinosaur.name} (${dinosaur.species})`);
8  },
9
10  // Find a dinosaur by name
11  findDinosaurByName: function(name) {
12    return this.activeDinosaurs.find(dino => dino.name === name);
13  },
14
15  // Trigger an alert procedure
16  triggerAlert: function(message) {
17    console.log(`SYSTEM ALERT: ${message}`);
18
19    // Notify about all active dinosaurs
20    this.activeDinosaurs.forEach(function(dino) {
21      console.log(`- ${dino.name} (${dino.species}) in sector ${dino.sector}`);
22    });
23  }
24};
25
26// Dinosaur class with a movement method
27class Dinosaur {
28  constructor(name, species, sector) {
29    this.name = name;
30    this.species = species;
31    this.sector = sector;
32    this.status = "calm";
33  }
34
35  move(newSector, monitoringSystem) {
36    // We can pass monitoringSystem as a parameter
37    console.log(`${this.name} is moving from sector ${this.sector} to sector ${newSector}`);
38    this.sector = newSector;
39
40    // Or use call() to invoke a method with the proper context
41    const alertMessage = `Dinosaur ${this.name} entered sector ${newSector}!`;
42    monitoringSystem.triggerAlert.call(monitoringSystem, alertMessage);
43  }
44
45  // Method that changes dinosaur status
46  updateStatus(newStatus) {
47    this.status = newStatus;
48    console.log(`Dinosaur ${this.name} status changed to: ${this.status}`);
49    return this;
50  }
51}
52
53// Initialize system and dinosaurs
54const rex = new Dinosaur("Rex", "Tyrannosaurus", "A");
55const blue = new Dinosaur("Blue", "Velociraptor", "B");
56const delta = new Dinosaur("Delta", "Velociraptor", "B");
57
58// Register dinosaurs in the system
59dinoMonitoringSystem.registerDinosaur(rex);
60dinoMonitoringSystem.registerDinosaur(blue);
61dinoMonitoringSystem.registerDinosaur(delta);
62
63// Move a dinosaur
64blue.move("C", dinoMonitoringSystem);
65
66// Method chaining (thanks to return this)
67rex.updateStatus("agitated").updateStatus("aggressive");
68
69// Using bind() to create a function with predefined 'this'
70const checkRexStatus = function() {
71  console.log(`T-Rex status: ${this.status}`);
72};
73
74const boundCheckStatus = checkRexStatus.bind(rex);
75boundCheckStatus(); // "T-Rex status: aggressive"

5. this in Arrow Functions

Arrow functions, introduced in ES6, handle this differently than traditional functions. They do not have their own this; instead, they "inherit" this from the surrounding lexical scope.

1const parkSystem = {
2  name: "Jurassic Park",
3  dinosaurs: ["T-Rex", "Velociraptor", "Triceratops"],
4
5  // Method using an arrow function
6  listDinosaurs: function() {
7    console.log(`${this.name} contains the following dinosaurs:`);
8
9    // Arrow function preserves 'this' from the listDinosaurs scope
10    this.dinosaurs.forEach(dino => {
11      console.log(`- ${dino} in park ${this.name}`);
12    });
13
14    // For comparison - a traditional function would lose 'this' context
15    /*
16    this.dinosaurs.forEach(function(dino) {
17      // 'this' refers to the global object or is undefined in strict mode
18      console.log(`- ${dino} in park ${this.name}`); // this.name would be undefined
19    });
20    */
21  }
22};
23
24parkSystem.listDinosaurs();
25// "Jurassic Park contains the following dinosaurs:"
26// "- T-Rex in park Jurassic Park"
27// "- Velociraptor in park Jurassic Park"
28// "- Triceratops in park Jurassic Park"

Arrow functions are especially useful when you want to preserve the this context in nested functions, callbacks, or asynchronous programming.

Practical Example: Monitoring System with Timers

1class DinosaurMonitor {
2  constructor(name, species) {
3    this.name = name;
4    this.species = species;
5    this.status = "Normal";
6    this.lastUpdated = new Date();
7    this.checkInterval = null;
8  }
9
10  // Start monitoring with regular status checks
11  startMonitoring() {
12    console.log(`Starting monitoring for dinosaur: ${this.name}`);
13
14    // Arrow function preserves the 'this' context
15    this.checkInterval = setInterval(() => {
16      this.checkStatus();
17    }, 5000);
18
19    // If we used a regular function, we would lose context:
20    /*
21    this.checkInterval = setInterval(function() {
22      // 'this' here refers to the global object, not to the DinosaurMonitor instance
23      this.checkStatus(); // Error: this.checkStatus is not a function
24    }, 5000);
25    */
26  }
27
28  // Check dinosaur status
29  checkStatus() {
30    const now = new Date();
31    const timeSinceLastUpdate = (now - this.lastUpdated) / 1000; // in seconds
32
33    console.log(`Checking status of dinosaur ${this.name} (${this.species})...`);
34    console.log(`Last updated: ${timeSinceLastUpdate.toFixed(1)} seconds ago`);
35    console.log(`Status: ${this.status}`);
36
37    this.lastUpdated = now;
38
39    // Simulate status change (in a real system, sensor data would go here)
40    if (Math.random() < 0.3) {
41      const possibleStatuses = ["Normal", "Agitated", "Resting", "Feeding", "Aggressive"];
42      this.status = possibleStatuses[Math.floor(Math.random() * possibleStatuses.length)];
43      console.log(`New status: ${this.status}`);
44    }
45  }
46
47  // Stop monitoring
48  stopMonitoring() {
49    if (this.checkInterval) {
50      clearInterval(this.checkInterval);
51      this.checkInterval = null;
52      console.log(`Stopped monitoring dinosaur: ${this.name}`);
53    }
54  }
55}
56
57// Create and start a monitor
58const blueMonitor = new DinosaurMonitor("Blue", "Velociraptor");
59blueMonitor.startMonitoring();
60
61// Stop the monitor after 20 seconds
62setTimeout(() => {
63  blueMonitor.stopMonitoring();
64}, 20000);

Common Pitfalls with this

The this keyword can be a source of many bugs in JavaScript. Here are some common pitfalls and how to avoid them:

1. Losing Context in Nested Functions

1const dinosaur = {
2  name: "Rex",
3  behaviors: ["hunting", "resting", "running"],
4
5  logBehaviors: function() {
6    // 'this' here refers to 'dinosaur'
7    console.log(`Dinosaur ${this.name} exhibits the following behaviors:`);
8
9    // Problem: 'this' in the forEach function refers to the global object
10    this.behaviors.forEach(function(behavior) {
11      // 'this.name' is undefined
12      console.log(`- ${this.name} likes ${behavior}`);
13    });
14  }
15};
16
17dinosaur.logBehaviors();
18// "Dinosaur Rex exhibits the following behaviors:"
19// "- undefined likes hunting"
20// "- undefined likes resting"
21// "- undefined likes running"

Solutions:

  1. Use an arrow function:
1const dinosaur = {
2  name: "Rex",
3  behaviors: ["hunting", "resting", "running"],
4
5  logBehaviors: function() {
6    console.log(`Dinosaur ${this.name} exhibits the following behaviors:`);
7
8    // Arrow function preserves 'this' from the surrounding scope
9    this.behaviors.forEach(behavior => {
10      console.log(`- ${this.name} likes ${behavior}`);
11    });
12  }
13};
  1. Store a reference to this in a variable:
1const dinosaur = {
2  name: "Rex",
3  behaviors: ["hunting", "resting", "running"],
4
5  logBehaviors: function() {
6    console.log(`Dinosaur ${this.name} exhibits the following behaviors:`);
7
8    // Store reference to 'this' in a variable 'self' or 'that'
9    const self = this;
10    this.behaviors.forEach(function(behavior) {
11      console.log(`- ${self.name} likes ${behavior}`);
12    });
13  }
14};
  1. Use the bind() method:
1const dinosaur = {
2  name: "Rex",
3  behaviors: ["hunting", "resting", "running"],
4
5  logBehaviors: function() {
6    console.log(`Dinosaur ${this.name} exhibits the following behaviors:`);
7
8    // Using bind() to attach 'this'
9    this.behaviors.forEach(function(behavior) {
10      console.log(`- ${this.name} likes ${behavior}`);
11    }.bind(this));
12  }
13};

2. Methods as Callbacks

When an object method is passed as a callback, it loses its this context:

1class DinosaurTracker {
2  constructor(name) {
3    this.name = name;
4    this.tracked = false;
5  }
6
7  startTracking() {
8    this.tracked = true;
9    console.log(`Started tracking dinosaur ${this.name}`);
10  }
11}
12
13const blueTracker = new DinosaurTracker("Blue");
14
15// Works correctly
16blueTracker.startTracking();
17
18// Doesn't work - 'this' does not refer to blueTracker
19setTimeout(blueTracker.startTracking, 1000); // this does not point to blueTracker

Solutions:

  1. Use an arrow function:
1setTimeout(() => blueTracker.startTracking(), 1000);
  1. Use bind():
1setTimeout(blueTracker.startTracking.bind(blueTracker), 1000);

Advanced Example: Park Management System

Below is a more complex example of a Jurassic Park management system that uses various aspects of the this keyword:

1// Main park management system
2const parkManagementSystem = {
3  parkName: "Jurassic Park",
4  status: "operational",
5  securityLevel: "normal",
6
7  // Park subsystems
8  subsystems: {
9    power: { status: "online", output: "100%" },
10    security: { status: "active", fences: "operational" },
11    visitorCenter: { status: "open", visitors: 120 }
12  },
13
14  // System initialization method
15  initialize: function() {
16    console.log(`Initializing park management system: ${this.parkName}`);
17
18    // Check all subsystems
19    this.checkAllSubsystems();
20
21    // Set up regular monitoring
22    this.startMonitoring();
23
24    return this;
25  },
26
27  // Check subsystem status
28  checkAllSubsystems: function() {
29    console.log("Checking status of all subsystems...");
30
31    // Using Object.entries and arrow function to preserve 'this' context
32    Object.entries(this.subsystems).forEach(([name, system]) => {
33      console.log(`- Subsystem ${name}: ${system.status}`);
34
35      // Call specific checks for each subsystem
36      this[`check${name.charAt(0).toUpperCase() + name.slice(1)}`]();
37    });
38  },
39
40  // Specific check methods for each subsystem
41  checkPower: function() {
42    const powerSystem = this.subsystems.power;
43    console.log(`  Checking power: ${powerSystem.status}, Output: ${powerSystem.output}`);
44  },
45
46  checkSecurity: function() {
47    const securitySystem = this.subsystems.security;
48    console.log(`  Checking security: ${securitySystem.status}, Fences: ${securitySystem.fences}`);
49  },
50
51  checkVisitorCenter: function() {
52    const visitorSystem = this.subsystems.visitorCenter;
53    console.log(`  Checking visitor center: ${visitorSystem.status}, Visitors: ${visitorSystem.visitors}`);
54  },
55
56  // Start regular monitoring
57  startMonitoring: function() {
58    console.log("Starting regular park monitoring...");
59
60    // Arrow function to preserve 'this' context
61    this.monitoringInterval = setInterval(() => {
62      this.checkSecurityStatus();
63    }, 10000);
64
65    // Simulate a method call after 5 seconds
66    setTimeout(() => this.simulateSecurityAlert(), 5000);
67  },
68
69  // Check security status
70  checkSecurityStatus: function() {
71    console.log(`[MONITOR] Park status: ${this.status}, Security level: ${this.securityLevel}`);
72  },
73
74  // Simulate a security alert
75  simulateSecurityAlert: function() {
76    console.log("SIMULATION: Security breach detected in sector B!");
77
78    // Change park status and security level
79    this.status = "alert";
80    this.securityLevel = "high";
81    console.log(`Park status changed to: ${this.status}`);
82    console.log(`Security level raised to: ${this.securityLevel}`);
83
84    // Direct status check
85    this.checkSecurityStatus();
86
87    // Create an emergency response team using a constructor
88    const responseTeam = new EmergencyResponseTeam(this);
89
90    // Pass method as callback while preserving context
91    responseTeam.respondToAlert(this.resolveSecurityAlert.bind(this));
92  },
93
94  // Resolve security alert
95  resolveSecurityAlert: function(report) {
96    console.log(`Received report from response team: ${report}`);
97
98    // Restore normal status
99    this.status = "operational";
100    this.securityLevel = "normal";
101    console.log("Security alert resolved.");
102    console.log(`Park status restored to: ${this.status}`);
103
104    // Check current status
105    this.checkSecurityStatus();
106  },
107
108  // Stop monitoring
109  stopMonitoring: function() {
110    if (this.monitoringInterval) {
111      clearInterval(this.monitoringInterval);
112      console.log("Park monitoring stopped.");
113    }
114  }
115};
116
117// Constructor for the emergency response team
118function EmergencyResponseTeam(park) {
119  // 'this' refers to the new object
120  this.teamName = "Emergency Response Team";
121  this.members = ["Owen Grady", "Claire Dearing", "Dr. Wu"];
122  this.assignedPark = park;
123
124  this.respondToAlert = function(callback) {
125    console.log(`${this.teamName} responding to alert at park: ${this.assignedPark.parkName}`);
126
127    // Simulate response time
128    setTimeout(() => {
129      console.log(`${this.teamName} has secured the area and restored fences.`);
130
131      // Call the callback with a report
132      if (typeof callback === 'function') {
133        callback("Threat neutralized, fences repaired");
134      }
135    }, 3000);
136  };
137}
138
139// Initialize and start the system
140parkManagementSystem.initialize();
141
142// Stop the system after 20 seconds
143setTimeout(() => {
144  parkManagementSystem.stopMonitoring();
145}, 20000);

Summary and Best Practices

The this keyword is a powerful tool in JavaScript, but it requires understanding how it behaves in different contexts:

  1. In the global context: this is window in the browser and module.exports ({}) in a Node.js file; in a regular function outside strict mode it is the global object (globalThis).

  2. In an object method: this refers to the object that called the method.

  3. In a constructor: this refers to the newly created object.

  4. In arrow functions: this is inherited from the surrounding lexical scope.

  5. With call/apply/bind: this is explicitly specified by the programmer.

Best Practices:

  1. Use arrow functions for callbacks and nested functions to preserve the this context.

  2. Use call(), apply(), or bind() methods when you need to explicitly control the value of this.

  3. Avoid creating methods in constructors; instead, add them to the prototype to avoid code duplication.

  4. Remember that this is determined when the function is called, not when it is defined.

  5. Use ES6 classes instead of traditional constructors for cleaner code.

In the next lesson, we will dive deeper into arrays in JavaScript, which are a key component of managing data collections in our Jurassic Park.

Code for this lesson: index.js
1// The 'this' keyword - Jurassic Park
2console.log("'this' Laboratory in JavaScript");
3console.log("Understanding execution context\n");
4
5// ===========================================
6// 1. 'this' BASICS
7// ===========================================
8console.log("=== 1. What is 'this'? ===");
9
10// 'this' refers to the object in whose context the code is executed
11
12const dinosaur = {
13  name: "Tyrannosaurus Rex",
14  age: 8,
15
16  roar: function() {
17    console.log(`\n${this.name} roars: ROAARRR!`);
18    console.log(`   Age: ${this.age} years`);
19  }
20};
21
22dinosaur.roar(); // 'this' points to the 'dinosaur' object
23
24// ===========================================
25// 2. 'this' IN DIFFERENT CONTEXTS
26// ===========================================
27console.log("\n=== 2. 'this' contexts ===");
28
29// Context 1: Object method
30const raptor = {
31  name: "Velociraptor",
32  speed: 40,
33
34  run: function() {
35    console.log(`\n${this.name} running at a speed of ${this.speed} km/h`);
36  }
37};
38
39raptor.run(); // this = raptor
40
41// Context 2: Global function (strict mode)
42function globalFunction() {
43  console.log("\nGlobal function - this:", typeof this);
44  // In strict mode: undefined
45  // Without strict mode: window (browser) or global (Node.js)
46}
47
48globalFunction();
49
50// Context 3: Constructor
51function Dinosaur(name, species) {
52  this.name = name;
53  this.species = species;
54  this.roar = function() {
55    console.log(`${this.name} (${this.species}): ROAARRR!`);
56  };
57}
58
59const trex = new Dinosaur("Rexy", "T-Rex");
60console.log("\nConstructor:");
61console.log(`   Name: ${trex.name}`);
62console.log(`   Species: ${trex.species}`);
63trex.roar();
64
65// ===========================================
66// 3. LOSS OF 'this' CONTEXT
67// ===========================================
68console.log("\n=== 3. Loss of 'this' context ===");
69
70const park = {
71  name: "Jurassic Park",
72  dinosaurs: ["T-Rex", "Velociraptor", "Triceratops"],
73
74  displayDinosaurs: function() {
75    console.log(`\nDinosaurs in ${this.name}:`);
76
77    // PROBLEM: forEach with a traditional function
78    // this.dinosaurs.forEach(function(dino) {
79    //   console.log(`   - ${dino} from ${this.name}`); // this.name = undefined!
80    // });
81
82    // SOLUTION 1: Arrow function
83    this.dinosaurs.forEach(dino => {
84      console.log(`   - ${dino} from ${this.name}`);
85    });
86  }
87};
88
89park.displayDinosaurs();
90
91// SOLUTION 2: bind()
92const park2 = {
93  name: "Isla Nublar",
94  dinosaurs: ["Stegosaurus", "Brachiosaurus"],
95
96  displayDinosaurs: function() {
97    console.log(`\nDinosaurs in ${this.name}:`);
98
99    this.dinosaurs.forEach(function(dino) {
100      console.log(`   - ${dino} from ${this.name}`);
101    }.bind(this)); // Binding this
102  }
103};
104
105park2.displayDinosaurs();
106
107// ===========================================
108// 4. METHODS: call(), apply(), bind()
109// ===========================================
110console.log("\n=== 4. Methods: call(), apply(), bind() ===");
111
112const dinoInfo = {
113  describe: function(habitat, diet) {
114    console.log(`\n${this.name}:`);
115    console.log(`   Environment: ${habitat}`);
116    console.log(`   Diet: ${diet}`);
117  }
118};
119
120const dino1 = { name: "T-Rex" };
121const dino2 = { name: "Triceratops" };
122
123// call() - a call with an explicit this and arguments
124dinoInfo.describe.call(dino1, "Tropical forest", "Carnivore");
125
126// apply() - like call(), but with the arguments in an array
127dinoInfo.describe.apply(dino2, ["Savanna", "Herbivore"]);
128
129// bind() - creates a new function with bound this
130const describeTRex = dinoInfo.describe.bind(dino1);
131describeTRex("Jungle", "Carnivore");
132
133// ===========================================
134// 5. ARROW FUNCTIONS AND 'this'
135// ===========================================
136console.log("\n=== 5. Arrow functions and 'this' ===");
137
138const enclosure = {
139  id: "A-7",
140  dinosaur: "Velociraptor",
141
142  // Traditional method
143  checkTraditional: function() {
144    console.log(`\nPaddock ${this.id} (traditional):`);
145
146    setTimeout(function() {
147      // console.log(`   Dinosaur: ${this.dinosaur}`); // undefined! this has changed
148      console.log("   this.dinosaur = undefined (context lost)");
149    }, 100);
150  },
151
152  // Solution with an arrow function
153  checkArrow: function() {
154    console.log(`\nPaddock ${this.id} (arrow):`);
155
156    setTimeout(() => {
157      console.log(`   Dinosaur: ${this.dinosaur}`); // Works! Arrow preserves this
158    }, 100);
159  }
160};
161
162enclosure.checkTraditional();
163
164setTimeout(() => {
165  enclosure.checkArrow();
166}, 150);
167
168// ===========================================
169// 6. 'this' IN CONSTRUCTORS AND CLASSES
170// ===========================================
171console.log("\n=== 6. 'this' in constructors and classes ===");
172
173// Constructor function
174function DinosaurPark(name) {
175  this.name = name;
176  this.dinosaurs = [];
177
178  this.addDinosaur = function(dino) {
179    this.dinosaurs.push(dino);
180    console.log(`Added ${dino} to ${this.name}`);
181  };
182
183  this.getCount = function() {
184    return this.dinosaurs.length;
185  };
186}
187
188const park1 = new DinosaurPark("Jurassic Park");
189park1.addDinosaur("T-Rex");
190park1.addDinosaur("Velociraptor");
191console.log(`Total dinosaurs: ${park1.getCount()}`);
192
193// ES6 Class
194class ModernPark {
195  constructor(name) {
196    this.name = name;
197    this.dinosaurs = [];
198  }
199
200  addDinosaur(dino) {
201    this.dinosaurs.push(dino);
202    console.log(`\nAdded ${dino} to ${this.name}`);
203  }
204
205  getCount() {
206    return this.dinosaurs.length;
207  }
208
209  displayAll() {
210    console.log(`\n${this.name} - Dinosaur list:`);
211    this.dinosaurs.forEach((dino, i) => {
212      console.log(`   ${i + 1}. ${dino}`);
213    });
214  }
215}
216
217const park3 = new ModernPark("Isla Nublar");
218park3.addDinosaur("Triceratops");
219park3.addDinosaur("Stegosaurus");
220park3.addDinosaur("Brachiosaurus");
221park3.displayAll();
222console.log(`Total: ${park3.getCount()}`);
223
224// ===========================================
225// 7. PROBLEM: METHOD AS CALLBACK
226// ===========================================
227console.log("\n=== 7. Problem: method as callback ===");
228
229const securitySystem = {
230  level: "HIGH",
231
232  checkSecurity: function() {
233    console.log(`Security level: ${this.level}`);
234  }
235};
236
237// PROBLEM: this will be lost
238setTimeout(securitySystem.checkSecurity, 100); // this.level = undefined
239
240// SOLUTION 1: Arrow function wrapper
241setTimeout(() => {
242  securitySystem.checkSecurity();
243}, 150);
244
245// SOLUTION 2: bind()
246setTimeout(securitySystem.checkSecurity.bind(securitySystem), 200);
247
248// ===========================================
249// 8. COMPLEX EXAMPLE
250// ===========================================
251console.log("\n=== 8. Park management system - complex example ===");
252
253class JurassicParkManagement {
254  constructor(parkName) {
255    this.parkName = parkName;
256    this.dinosaurs = [];
257    this.visitors = 0;
258    this.status = "CLOSED";
259  }
260
261  openPark() {
262    this.status = "OPEN";
263    console.log(`\n${this.parkName} - Park open`);
264  }
265
266  closePark() {
267    this.status = "CLOSED";
268    console.log(`\n${this.parkName} - Park closed`);
269  }
270
271  addDinosaur(name, species, sector) {
272    const dino = { name, species, sector, health: 100 };
273    this.dinosaurs.push(dino);
274    console.log(`Added ${name} (${species}) to sector ${sector}`);
275  }
276
277  addVisitors(count) {
278    this.visitors += count;
279    console.log(`Added ${count} visitors. Total: ${this.visitors}`);
280  }
281
282  checkDinosaur(name) {
283    const dino = this.dinosaurs.find(d => d.name === name);
284    if (dino) {
285      console.log(`\nChecking ${name}:`);
286      console.log(`   Species: ${dino.species}`);
287      console.log(`   Sector: ${dino.sector}`);
288      console.log(`   Health: ${dino.health}%`);
289    } else {
290      console.log(`Dinosaur not found: ${name}`);
291    }
292  }
293
294  getDailyReport() {
295    console.log(`\nDAILY REPORT - ${this.parkName}`);
296    console.log("=".repeat(50));
297    console.log(`Status: ${this.status}`);
298    console.log(`Dinosaurs: ${this.dinosaurs.length}`);
299    console.log(`Visitors: ${this.visitors}`);
300
301    if (this.dinosaurs.length > 0) {
302      console.log("\nDinosaur list:");
303      this.dinosaurs.forEach((dino, i) => {
304        console.log(`  ${i + 1}. ${dino.name} (${dino.species}) - Sector ${dino.sector}`);
305      });
306    }
307  }
308
309  // Method using setTimeout with an arrow function
310  scheduleFeeding(dinoName, delay = 1000) {
311    console.log(`\n⏰ Feeding scheduled for ${dinoName} in ${delay}ms`);
312
313    setTimeout(() => {
314      const dino = this.dinosaurs.find(d => d.name === dinoName);
315      if (dino) {
316        console.log(`Feeding ${dinoName} in ${this.parkName}`);
317        dino.health = Math.min(100, dino.health + 10);
318      }
319    }, delay);
320  }
321}
322
323// System demonstration
324const management = new JurassicParkManagement("Isla Nublar");
325
326management.openPark();
327management.addDinosaur("Rexy", "Tyrannosaurus Rex", "A");
328management.addDinosaur("Blue", "Velociraptor", "B");
329management.addDinosaur("Trike", "Triceratops", "C");
330
331management.addVisitors(500);
332management.addVisitors(300);
333
334management.checkDinosaur("Blue");
335management.getDailyReport();
336
337management.scheduleFeeding("Rexy", 500);
338management.scheduleFeeding("Blue", 600);
339
340setTimeout(() => {
341  console.log("\n--- After feeding ---");
342  management.checkDinosaur("Rexy");
343  management.checkDinosaur("Blue");
344
345  management.closePark();
346
347  console.log("\nCongratulations! You've mastered the 'this' keyword!");
348}, 800);

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What does the 'this' keyword mean in JavaScript?

  2. 2. What does 'this' point to inside an object method in JavaScript?

  3. 3. How does 'this' behave in arrow functions?

  4. 4. In what context does 'this' point to the newly created object?

Useful articles