JavaScript and TypeScript course Β· Module 2: Functions, Arrays and Objects

Object Properties and Methods

13 min read
In this lesson9

In the Jurassic Park management system, object properties and methods are key to modeling and controlling the behavior of the various parts of our complex. In this lesson we will take a closer look at the properties and methods of JavaScript objects and learn advanced techniques that help us keep our park and its residents in perfect condition.

Property Descriptors

In JavaScript every object property has not only a value, but also attributes that control how that property can be used. These attributes are known as property descriptors.

1const dinosaur = {
2  name: "Blue"
3};
4
5// Getting a property descriptor
6const descriptor = Object.getOwnPropertyDescriptor(dinosaur, "name");
7console.log(descriptor);
8// Output: { value: "Blue", writable: true, enumerable: true, configurable: true }

A property descriptor contains the following fields:

  • value: the current value of the property
  • writable: whether the property can be changed
  • enumerable: whether the property shows up during iteration (e.g. in a for...in loop)
  • configurable: whether the descriptor can be changed and whether the property can be deleted

Defining Properties with Descriptors

We can define new properties with specific descriptors using Object.defineProperty():

1const velociraptor = {};
2
3// We define the "species" property with specific descriptors
4Object.defineProperty(velociraptor, "species", {
5  value: "Velociraptor",
6  writable: false, // We cannot change the value
7  enumerable: true, // It will appear during iteration
8  configurable: false // We cannot change the descriptor or delete the property
9});
10
11console.log(velociraptor.species); // "Velociraptor"
12
13// An attempt to change it will not work (in strict mode it throws an error)
14velociraptor.species = "Tyrannosaurus";
15console.log(velociraptor.species); // Still "Velociraptor"
16
17// An attempt to delete it will not work
18delete velociraptor.species; // Returns false
19console.log(velociraptor.species); // Still "Velociraptor"

We can also define several properties at once:

1const triceratops = {};
2
3Object.defineProperties(triceratops, {
4  species: {
5    value: "Triceratops",
6    writable: false
7  },
8  diet: {
9    value: "herbivore",
10    writable: false
11  },
12  age: {
13    value: 5,
14    writable: true // Age can change
15  }
16});
17
18console.log(triceratops.species); // "Triceratops"
19console.log(triceratops.diet); // "herbivore"
20console.log(triceratops.age); // 5
21
22triceratops.age = 6;
23console.log(triceratops.age); // 6

Getters and Setters

Getters and setters are special methods that let you control access to an object's properties.

1const dinosaur = {
2  _health: 100, // The underscore convention signals that this is a private property
3
4  // Getter - runs when we try to read the health property
5  get health() {
6    console.log("Reading the health level");
7    return this._health;
8  },
9
10  // Setter - runs when we try to assign a value to the health property
11  set health(value) {
12    if (value < 0) {
13      console.log("Warning: Attempt to set health below 0");
14      this._health = 0;
15    } else if (value > 100) {
16      console.log("Warning: Attempt to set health above 100");
17      this._health = 100;
18    } else {
19      console.log(`Setting the health level to ${value}`);
20      this._health = value;
21    }
22  }
23};
24
25console.log(dinosaur.health); // Runs the getter, result: 100
26dinosaur.health = 80; // Runs the setter
27console.log(dinosaur.health); // 80
28dinosaur.health = 150; // "Warning: Attempt to set health above 100"
29console.log(dinosaur.health); // 100
30dinosaur.health = -20; // "Warning: Attempt to set health below 0"
31console.log(dinosaur.health); // 0

We can also define getters and setters using Object.defineProperty():

1const dinosaur = {
2  _name: "Rex",
3  _species: "Tyrannosaurus"
4};
5
6Object.defineProperty(dinosaur, "fullName", {
7  get: function() {
8    return `${this._name} the ${this._species}`;
9  },
10
11  set: function(value) {
12    // We assume that value has the format "Name the Species"
13    const parts = value.split(" the ");
14    if (parts.length === 2) {
15      this._name = parts[0];
16      this._species = parts[1];
17    } else {
18      console.log("Invalid format. Use: 'Name the Species'");
19    }
20  },
21
22  enumerable: true,
23  configurable: true
24});
25
26console.log(dinosaur.fullName); // "Rex the Tyrannosaurus"
27dinosaur.fullName = "Blue the Velociraptor";
28console.log(dinosaur._name); // "Blue"
29console.log(dinosaur._species); // "Velociraptor"

Object Methods and Their Advanced Features

Binding this - the bind Method

The this keyword in methods refers to the object on which the method is called. However, in some cases (e.g. when we pass a method as a callback) we can lose the this context. The bind method lets you set the value of this explicitly.

1const rex = {
2  name: "Rex",
3  makeSound: function() {
4    console.log(`${this.name} roars loudly!`);
5  }
6};
7
8// Normal behavior
9rex.makeSound(); // "Rex roars loudly!"
10
11// Problematic use - losing the this context
12const rexSound = rex.makeSound;
13// rexSound(); // Error: this.name is undefined
14
15// Solution - using bind to set the value of this
16const boundRexSound = rex.makeSound.bind(rex);
17boundRexSound(); // "Rex roars loudly!"
18
19// We can also set this to a different object
20const blue = { name: "Blue" };
21const blueSound = rex.makeSound.bind(blue);
22blueSound(); // "Blue roars loudly!"

The call and apply Methods

The call and apply methods let you call a function with a specified this value and arguments:

1function attack(target, damage) {
2  console.log(`${this.name} attacks ${target} and deals ${damage} damage!`);
3}
4
5const trex = { name: "Tyrannosaurus Rex" };
6const raptor = { name: "Velociraptor" };
7
8// We use call with explicitly passed arguments
9attack.call(trex, "Triceratops", 50);
10// "Tyrannosaurus Rex attacks Triceratops and deals 50 damage!"
11
12// We use apply with the arguments in an array
13attack.apply(raptor, ["Gallimimus", 30]);
14// "Velociraptor attacks Gallimimus and deals 30 damage!"

Object Properties as Symbols

Symbols provide unique identifiers that can be used as object property keys. They are especially useful for creating "private" properties or for extending objects without the risk of name collisions.

1// Creating symbols
2const healthSymbol = Symbol('health');
3const nameSymbol = Symbol('name');
4
5const dinosaur = {
6  [nameSymbol]: "Blue",
7  [healthSymbol]: 100,
8  species: "Velociraptor"
9};
10
11console.log(dinosaur[nameSymbol]); // "Blue"
12console.log(dinosaur[healthSymbol]); // 100
13
14// Symbols are not visible in for...in loops
15for (let key in dinosaur) {
16  console.log(key); // Only "species", without nameSymbol and healthSymbol
17}
18
19// Nor in Object.keys()
20console.log(Object.keys(dinosaur)); // ["species"]
21
22// But you can get them with Object.getOwnPropertySymbols()
23console.log(Object.getOwnPropertySymbols(dinosaur)); // [Symbol(name), Symbol(health)]

Well-Known Symbols

JavaScript defines several "well-known symbols" that let classes customize their behavior. The most commonly used one is Symbol.iterator, which makes objects iterable.

1// We create a DinosaurPack object that will be iterable
2class DinosaurPack {
3  constructor() {
4    this.dinosaurs = [];
5  }
6
7  addDinosaur(name, species) {
8    this.dinosaurs.push({ name, species });
9  }
10
11  // Implementing iterability
12  [Symbol.iterator]() {
13    let index = 0;
14    const dinosaurs = this.dinosaurs;
15
16    return {
17      next: function() {
18        if (index < dinosaurs.length) {
19          return { value: dinosaurs[index++], done: false };
20        } else {
21          return { done: true };
22        }
23      }
24    };
25  }
26}
27
28const pack = new DinosaurPack();
29pack.addDinosaur("Blue", "Velociraptor");
30pack.addDinosaur("Delta", "Velociraptor");
31pack.addDinosaur("Echo", "Velociraptor");
32pack.addDinosaur("Charlie", "Velociraptor");
33
34// Now we can iterate over our object
35for (const dino of pack) {
36  console.log(`${dino.name} is a ${dino.species}`);
37}
38// Output:
39// "Blue is a Velociraptor"
40// "Delta is a Velociraptor"
41// "Echo is a Velociraptor"
42// "Charlie is a Velociraptor"

Freezing, Sealing and Preventing Extensions of Objects

JavaScript offers three methods for controlling how far objects can be modified:

Object.freeze()

Freezes an object (shallowly: nested objects can still be changed) - you cannot change existing properties or add new ones.

1const securityProtocol = {
2  level: "Maximum",
3  status: "Active",
4  lastUpdated: "2023-01-15"
5};
6
7// We freeze the object
8Object.freeze(securityProtocol);
9
10// Attempts to modify it will not work
11securityProtocol.level = "Minimum"; // Will not change
12securityProtocol.newProperty = "Value"; // Will not be added
13
14console.log(securityProtocol.level); // Still "Maximum"
15console.log(securityProtocol.newProperty); // undefined
16
17console.log(Object.isFrozen(securityProtocol)); // true

Object.seal()

Seals an object - you can modify existing properties, but you cannot add new ones or delete existing ones.

1const dinoStats = {
2  health: 100,
3  hunger: 50
4};
5
6// We seal the object
7Object.seal(dinoStats);
8
9// We can modify existing properties
10dinoStats.health = 80;
11console.log(dinoStats.health); // 80
12
13// But we cannot add new ones
14dinoStats.happiness = 90; // Will not work
15console.log(dinoStats.happiness); // undefined
16
17// Nor delete existing ones
18delete dinoStats.hunger; // Will not work
19console.log(dinoStats.hunger); // Still 50
20
21console.log(Object.isSealed(dinoStats)); // true

Object.preventExtensions()

Prevents an object from being extended - you cannot add new properties, but you can modify and delete existing ones.

1const dinosaur = {
2  name: "Blue",
3  species: "Velociraptor"
4};
5
6// We prevent extensions
7Object.preventExtensions(dinosaur);
8
9// We cannot add new properties
10dinosaur.age = 4; // Will not work
11console.log(dinosaur.age); // undefined
12
13// But we can modify existing ones
14dinosaur.name = "Delta";
15console.log(dinosaur.name); // "Delta"
16
17// And delete existing ones
18delete dinosaur.species;
19console.log(dinosaur.species); // undefined
20
21console.log(Object.isExtensible(dinosaur)); // false

The Proxy Object

The Proxy object lets you intercept and customize basic operations on objects.

1const dinosaur = {
2  name: "Blue",
3  species: "Velociraptor",
4  health: 100,
5  hunger: 50,
6  aggressionLevel: 8
7};
8
9// We create a proxy for the dinosaur object
10const monitoredDinosaur = new Proxy(dinosaur, {
11  // We intercept the property read operation
12  get: function(target, prop, receiver) {
13    console.log(`Reading property: ${prop}`);
14    return Reflect.get(target, prop, receiver);
15  },
16
17  // We intercept the property write operation
18  set: function(target, prop, value, receiver) {
19    console.log(`Setting ${prop} to ${value}`);
20
21    // We add validation for some properties
22    if (prop === 'health') {
23      if (value < 0) value = 0;
24      if (value > 100) value = 100;
25
26      // Logging the health change
27      const oldValue = target[prop];
28      const change = value - oldValue;
29      console.log(`Health change: ${change > 0 ? '+' : ''}${change}%`);
30
31      // Checking whether the dinosaur is seriously injured
32      if (value < 20 && oldValue >= 20) {
33        console.log("ALARM: The dinosaur is seriously injured!");
34      }
35    }
36
37    if (prop === 'aggressionLevel') {
38      if (value > 7) {
39        console.log("WARNING: High aggression level - security measures must be increased!");
40      }
41    }
42
43    return Reflect.set(target, prop, value, receiver);
44  },
45
46  // We intercept the property delete operation
47  deleteProperty: function(target, prop) {
48    console.log(`Attempt to delete ${prop} - operation forbidden to keep the data safe`);
49    return false; // We do not allow properties to be deleted
50  }
51});
52
53// Testing our proxy
54console.log(monitoredDinosaur.name); // "Reading property: name", "Blue"
55monitoredDinosaur.health = 90; // "Setting health to 90", "Health change: -10%"
56monitoredDinosaur.health = 15; // "Setting health to 15", "Health change: -75%", "ALARM: The dinosaur is seriously injured!"
57monitoredDinosaur.aggressionLevel = 9; // "Setting aggressionLevel to 9", "WARNING: High aggression level..."
58delete monitoredDinosaur.hunger; // "Attempt to delete hunger - operation forbidden to keep the data safe"

In Practice: A Dinosaur Health Monitoring System

Let's combine several of the techniques we have learned to build an advanced dinosaur health monitoring system for our park:

1class DinosaurHealthMonitor {
2  constructor() {
3    this.dinosaurs = new Map();
4    this.alarmThreshold = 30;
5    this.criticalThreshold = 15;
6    this.lastCheckTime = null;
7  }
8
9  // We add a dinosaur to the monitoring system
10  addDinosaur(id, name, species) {
11    if (this.dinosaurs.has(id)) {
12      console.log(`A dinosaur with ID ${id} already exists in the system.`);
13      return false;
14    }
15
16    const dinosaurData = {
17      id,
18      name,
19      species,
20      _health: 100,
21      _lastFed: new Date(),
22      _status: "Healthy",
23
24      // We use a getter and a setter for health
25      get health() {
26        return this._health;
27      },
28
29      set health(value) {
30        const oldHealth = this._health;
31        this._health = Math.max(0, Math.min(100, value)); // Clamp to the 0-100 range
32
33        // Update the status based on health
34        if (this._health === 0) {
35          this._status = "Critical - Emergency Response Required";
36        } else if (this._health < 15) {
37          this._status = "Critical";
38        } else if (this._health < 30) {
39          this._status = "Poor - Medical Attention Required";
40        } else if (this._health < 60) {
41          this._status = "Fair";
42        } else if (this._health < 90) {
43          this._status = "Good";
44        } else {
45          this._status = "Excellent";
46        }
47
48        return this._health;
49      },
50
51      // Method for feeding the dinosaur
52      feed() {
53        const now = new Date();
54        const hoursSinceLastFed = (now - this._lastFed) / (1000 * 60 * 60);
55
56        // The effect of feeding depends on how long ago the dinosaur was last fed
57        if (hoursSinceLastFed < 1) {
58          console.log(`${this.name} is not hungry (fed less than an hour ago).`);
59          return false;
60        } else {
61          const healthGain = Math.min(10, hoursSinceLastFed * 2); // At most 10 health points
62          this.health += healthGain;
63          this._lastFed = now;
64
65          console.log(`${this.name} has been fed. +${healthGain.toFixed(1)} health. Current level: ${this.health}%`);
66          return true;
67        }
68      }
69    };
70
71    // We use Object.seal to prevent new properties from being added
72    Object.seal(dinosaurData);
73
74    // We add the dinosaur to the map
75    this.dinosaurs.set(id, dinosaurData);
76    console.log(`Added dinosaur ${name} (${species}) to the health monitoring system.`);
77    return true;
78  }
79
80  // Getting information about a dinosaur
81  getDinosaurInfo(id) {
82    if (!this.dinosaurs.has(id)) {
83      return `A dinosaur with ID ${id} does not exist in the system.`;
84    }
85
86    const dino = this.dinosaurs.get(id);
87    return {
88      id: dino.id,
89      name: dino.name,
90      species: dino.species,
91      health: dino.health,
92      status: dino._status,
93      lastFed: dino._lastFed
94    };
95  }
96
97  // Method that simulates the passage of time, which lowers the dinosaurs' health
98  simulateTimePass(hours) {
99    console.log(`Simulating the passage of ${hours} hours...`);
100
101    this.dinosaurs.forEach(dino => {
102      // Health reduction - the rate depends on the species
103      let healthReduction = hours;
104
105      if (dino.species.toLowerCase().includes('tyrannosaurus')) {
106        healthReduction *= 1.5; // Tyrannosaurus loses health faster
107      } else if (dino.species.toLowerCase().includes('velociraptor')) {
108        healthReduction *= 1.2; // Velociraptors fairly fast too
109      }
110
111      const newHealth = dino.health - healthReduction;
112      dino.health = newHealth;
113
114      console.log(`${dino.name}: Health reduced by ${healthReduction.toFixed(1)} to ${dino.health}% (${dino._status})`);
115    });
116
117    this.lastCheckTime = new Date();
118    this.checkAlarms();
119  }
120
121  // Checking health alarms
122  checkAlarms() {
123    console.log("Checking health alarms...");
124
125    const criticalDinos = [];
126    const alarmDinos = [];
127
128    this.dinosaurs.forEach(dino => {
129      if (dino.health <= this.criticalThreshold) {
130        criticalDinos.push(dino);
131      } else if (dino.health <= this.alarmThreshold) {
132        alarmDinos.push(dino);
133      }
134    });
135
136    if (criticalDinos.length > 0) {
137      console.log("=== CRITICAL HEALTH CONDITION ===");
138      criticalDinos.forEach(dino => {
139        console.log(`- ${dino.name} (${dino.species}): ${dino.health}% - REQUIRES IMMEDIATE INTERVENTION!`);
140      });
141    }
142
143    if (alarmDinos.length > 0) {
144      console.log("=== HEALTH ALARM ===");
145      alarmDinos.forEach(dino => {
146        console.log(`- ${dino.name} (${dino.species}): ${dino.health}% - Needs veterinary care`);
147      });
148    }
149
150    if (criticalDinos.length === 0 && alarmDinos.length === 0) {
151      console.log("All dinosaurs are in good health.");
152    }
153  }
154
155  // Generating a report for all dinosaurs
156  generateHealthReport() {
157    const now = new Date();
158    let report = `=== DINOSAUR HEALTH REPORT (generated: ${now.toLocaleString()}) ===\n`;
159
160    if (this.dinosaurs.size === 0) {
161      report += "No dinosaurs in the monitoring system.\n";
162    } else {
163      // Sorting the dinosaurs by health level (lowest first)
164      const sortedDinos = [...this.dinosaurs.values()].sort((a, b) => a.health - b.health);
165
166      sortedDinos.forEach(dino => {
167        report += `${dino.name} (${dino.species})\n`;
168        report += `  Health: ${dino.health}% (${dino._status})\n`;
169        report += `  Last fed: ${dino._lastFed.toLocaleString()}\n`;
170
171        // We add recommendations
172        if (dino.health < this.criticalThreshold) {
173          report += "  RECOMMENDATION: Immediate veterinary intervention! Transfer to the intensive care unit.\n";
174        } else if (dino.health < this.alarmThreshold) {
175          report += "  RECOMMENDATION: More frequent feeding and veterinary checks.\n";
176        }
177
178        report += "\n";
179      });
180    }
181
182    return report;
183  }
184}
185
186// Usage example
187const healthMonitor = new DinosaurHealthMonitor();
188
189// We add a few dinosaurs
190healthMonitor.addDinosaur("TRX-01", "Rex", "Tyrannosaurus Rex");
191healthMonitor.addDinosaur("VEL-01", "Blue", "Velociraptor");
192healthMonitor.addDinosaur("TRI-01", "Trixie", "Triceratops");
193healthMonitor.addDinosaur("BRACH-01", "Bronty", "Brachiosaurus");
194
195// We generate the initial report
196console.log(healthMonitor.generateHealthReport());
197
198// We simulate the passage of time
199healthMonitor.simulateTimePass(24); // 24 hours
200
201// We feed some of the dinosaurs
202const rex = healthMonitor.dinosaurs.get("TRX-01");
203const blue = healthMonitor.dinosaurs.get("VEL-01");
204rex.feed();
205blue.feed();
206
207// We simulate more time passing
208healthMonitor.simulateTimePass(48); // 48 hours
209
210// We generate the final report
211console.log(healthMonitor.generateHealthReport());

Summary

In this lesson we explored advanced properties and methods of JavaScript objects:

  1. Property descriptors - give you precise control over the attributes of properties
  2. Getters and setters - let you run code when a property is read or written
  3. Binding this - the bind, call and apply methods help you control the context of a function
  4. Symbols - provide unique identifiers for object properties
  5. Freezing objects - the freeze, seal and preventExtensions methods let you limit how far objects can be modified
  6. Proxy - lets you intercept and customize basic operations on objects

These techniques are essential when building complex systems such as our dinosaur health monitoring system in Jurassic Park. They let you create more reliable, secure and elegant software solutions.

Code for this lesson: index.js
1// Object properties and methods - Jurassic Park
2console.log("Object Properties and Methods");
3console.log("Advanced operations on objects\n");
4
5// ===========================================
6// 1. ACCESSING PROPERTIES
7// ===========================================
8console.log("=== 1. Access to properties ===");
9
10const dinosaur = {
11  name: "T-Rex",
12  species: "Tyrannosaurus Rex",
13  age: 8,
14  "sector-id": "A-7", // Property with a hyphen
15  weight: 8000
16};
17
18// Dot notation
19console.log("\nDot notation:");
20console.log(`   name: ${dinosaur.name}`);
21console.log(`   age: ${dinosaur.age}`);
22
23// Bracket notation
24console.log("\nBracket notation:");
25console.log(`   ['species']: ${dinosaur['species']}`);
26console.log(`   ['sector-id']: ${dinosaur['sector-id']}`); // Required for hyphens
27
28// Dynamic access
29const prop = "weight";
30console.log(`\nDynamic access [${prop}]: ${dinosaur[prop]}`);
31
32// ===========================================
33// 2. ADDING AND MODIFYING PROPERTIES
34// ===========================================
35console.log("\n=== 2. Adding and modifying ===");
36
37const dino = {
38  name: "Blue",
39  species: "Velociraptor"
40};
41
42console.log("\nInitially:", dino);
43
44// Adding new properties
45dino.age = 5;
46dino.weight = 80;
47dino["sector"] = "B";
48
49console.log("After adding:", dino);
50
51// Modifying existing
52dino.age = 6;
53dino.weight = 85;
54
55console.log("After modification:", dino);
56
57// ===========================================
58// 3. REMOVING PROPERTIES
59// ===========================================
60console.log("\n=== 3. Removing properties ===");
61
62const tempDino = {
63  name: "Charlie",
64  species: "Velociraptor",
65  tempId: 12345,
66  debugMode: true
67};
68
69console.log("\nBefore removal:", tempDino);
70
71delete tempDino.tempId;
72delete tempDino.debugMode;
73
74console.log("After removal:", tempDino);
75
76// ===========================================
77// 4. CHECKING PROPERTY EXISTENCE
78// ===========================================
79console.log("\n=== 4. Checking existence ===");
80
81const checkDino = {
82  name: "Rexy",
83  age: 8,
84  sector: null
85};
86
87// The 'in' operator
88console.log("\nThe 'in' operator:");
89console.log(`   'name' in checkDino: ${'name' in checkDino}`);
90console.log(`   'weight' in checkDino: ${'weight' in checkDino}`);
91console.log(`   'sector' in checkDino: ${'sector' in checkDino}`); // true despite null!
92
93// hasOwnProperty()
94console.log("\nhasOwnProperty():");
95console.log(`   hasOwnProperty('name'): ${checkDino.hasOwnProperty('name')}`);
96console.log(`   hasOwnProperty('toString'): ${checkDino.hasOwnProperty('toString')}`); // false (inherited)
97
98// undefined check
99console.log("\nChecking undefined:");
100console.log(`   name !== undefined: ${checkDino.name !== undefined}`);
101console.log(`   weight !== undefined: ${checkDino.weight !== undefined}`);
102
103// ===========================================
104// 5. ITERATING OVER PROPERTIES
105// ===========================================
106console.log("\n=== 5. Iteration over properties ===");
107
108const iterDino = {
109  name: "Trike",
110  species: "Triceratops",
111  age: 7,
112  weight: 9000,
113  sector: "C"
114};
115
116// for...in
117console.log("\nfor...in:");
118for (const key in iterDino) {
119  console.log(`   ${key}: ${iterDino[key]}`);
120}
121
122// Object.keys()
123console.log("\nObject.keys():");
124const keys = Object.keys(iterDino);
125console.log(`   Keys: ${keys.join(", ")}`);
126
127// Object.values()
128console.log("\nObject.values():");
129const values = Object.values(iterDino);
130console.log(`   Values: ${values.join(", ")}`);
131
132// Object.entries()
133console.log("\nObject.entries():");
134Object.entries(iterDino).forEach(([key, value]) => {
135  console.log(`   ${key} => ${value}`);
136});
137
138// ===========================================
139// 6. OBJECT METHODS
140// ===========================================
141console.log("\n=== 6. Object methods ===");
142
143const methodDino = {
144  name: "Stego",
145  species: "Stegosaurus",
146  health: 85,
147
148  // Traditional method
149  getInfo: function() {
150    return `${this.name} (${this.species})`;
151  },
152
153  // Shorthand method (ES6)
154  roar() {
155    return `${this.name} says: ROAARRR!`;
156  },
157
158  // Method with parameters
159  updateHealth(change) {
160    this.health += change;
161    if (this.health > 100) this.health = 100;
162    if (this.health < 0) this.health = 0;
163    return `Health: ${this.health}%`;
164  },
165
166  // Method returning an object
167  getStats() {
168    return {
169      name: this.name,
170      health: this.health,
171      status: this.health >= 80 ? "Healthy" : "Needs attention"
172    };
173  }
174};
175
176console.log("\n", methodDino.getInfo());
177console.log("   ", methodDino.roar());
178console.log("   ", methodDino.updateHealth(10));
179console.log("   Stats:", methodDino.getStats());
180
181// ===========================================
182// 7. THIS IN METHODS
183// ===========================================
184console.log("\n=== 7. The 'this' context in methods ===");
185
186const thisDino = {
187  name: "Brachio",
188  sector: "D",
189
190  showInfo() {
191    console.log(`\n${this.name} in sector ${this.sector}`);
192  },
193
194  // Problem with a nested function
195  showWithTraditional() {
196    console.log(`\n${this.name}:`);
197
198    ["Feeding", "Examination", "Cleaning"].forEach(function(task) {
199      // this here does NOT refer to thisDino!
200      console.log(`   - ${task}`); // We cannot use this.name
201    });
202  },
203
204  // Solution with an arrow function
205  showWithArrow() {
206    console.log(`\n${this.name}:`);
207
208    ["Feeding", "Examination", "Cleaning"].forEach(task => {
209      console.log(`   - ${task} for ${this.name}`); // this works!
210    });
211  }
212};
213
214thisDino.showInfo();
215thisDino.showWithTraditional();
216thisDino.showWithArrow();
217
218// ===========================================
219// 8. COMPUTED PROPERTY NAMES
220// ===========================================
221console.log("\n=== 8. Computed property names ===");
222
223const propName = "species";
224const propValue = "Velociraptor";
225const sectorNumber = 7;
226
227const computedDino = {
228  name: "Echo",
229  [propName]: propValue, // Computed property name
230  [`sector-${sectorNumber}`]: true,
231  [`health_${Date.now()}`]: 95
232};
233
234console.log("\nObject with computed names:");
235console.log(computedDino);
236
237// ===========================================
238// 9. PROPERTY DESCRIPTORS
239// ===========================================
240console.log("\n=== 9. Property descriptors ===");
241
242const descriptorDino = {};
243
244// Defining properties with descriptors
245Object.defineProperty(descriptorDino, 'name', {
246  value: "Rex",
247  writable: true,
248  enumerable: true,
249  configurable: true
250});
251
252Object.defineProperty(descriptorDino, 'id', {
253  value: 12345,
254  writable: false, // Read-only
255  enumerable: false, // Does not appear in for...in
256  configurable: false // Cannot remove or change the descriptor
257});
258
259console.log("\nProperties:");
260console.log(`   name: ${descriptorDino.name}`);
261console.log(`   id: ${descriptorDino.id}`);
262
263console.log("\nModification attempt:");
264descriptorDino.name = "Rexy"; // Will work
265console.log(`   name after the change: ${descriptorDino.name}`);
266
267descriptorDino.id = 99999; // Won't work (writable: false)
268console.log(`   id after the change attempt: ${descriptorDino.id}`);
269
270console.log("\nfor...in (id does not appear):");
271for (const key in descriptorDino) {
272  console.log(`   ${key}: ${descriptorDino[key]}`);
273}
274
275// ===========================================
276// 10. COMPLEX EXAMPLE
277// ===========================================
278console.log("\n=== 10. Dinosaur management system - complex example ===");
279
280class DinosaurManager {
281  constructor(name, species) {
282    // Basic properties
283    this._name = name;
284    this._species = species;
285    this._health = 100;
286    this._fed = false;
287    this._lastFed = null;
288    this._activities = [];
289
290    // Computed properties
291    Object.defineProperty(this, 'id', {
292      value: `DINO-${Date.now()}`,
293      writable: false,
294      enumerable: true
295    });
296  }
297
298  // Getters and setters
299  get name() {
300    return this._name;
301  }
302
303  get species() {
304    return this._species;
305  }
306
307  get health() {
308    return this._health;
309  }
310
311  set health(value) {
312    if (value < 0) value = 0;
313    if (value > 100) value = 100;
314
315    const oldHealth = this._health;
316    this._health = value;
317
318    this.logActivity(`Health changed from ${oldHealth}% to ${value}%`);
319  }
320
321  get status() {
322    if (this._health >= 80) return "Excellent";
323    if (this._health >= 50) return "Good";
324    if (this._health >= 20) return "Weak";
325    return "Critical";
326  }
327
328  get needsFeeding() {
329    if (!this._lastFed) return true;
330    const hoursSinceLastFed = (Date.now() - this._lastFed) / (1000 * 60 * 60);
331    return hoursSinceLastFed > 8;
332  }
333
334  // Action methods
335  feed() {
336    this._fed = true;
337    this._lastFed = Date.now();
338    this.health += 5;
339    this.logActivity("Fed");
340    return `${this._name} has been fed`;
341  }
342
343  heal(amount) {
344    this.health += amount;
345    this.logActivity(`Healed (+${amount}% health)`);
346    return `${this._name} healed by ${amount}%`;
347  }
348
349  exercise() {
350    this.health -= 5;
351    this.logActivity("Exercised");
352    return `${this._name} performed exercises`;
353  }
354
355  // Helper methods
356  logActivity(activity) {
357    this._activities.push({
358      activity,
359      timestamp: new Date().toISOString(),
360      health: this._health
361    });
362  }
363
364  getActivityLog(limit = 5) {
365    return this._activities.slice(-limit);
366  }
367
368  getInfo() {
369    return {
370      id: this.id,
371      name: this._name,
372      species: this._species,
373      health: this._health,
374      status: this.status,
375      fed: this._fed,
376      needsFeeding: this.needsFeeding,
377      activitiesCount: this._activities.length
378    };
379  }
380
381  displayReport() {
382    console.log(`\n==================================================`);
383    console.log(`DINOSAUR REPORT: ${this._name}`);
384    console.log("=".repeat(50));
385
386    const info = this.getInfo();
387    console.log(`ID: ${info.id}`);
388    console.log(`Species: ${info.species}`);
389    console.log(`Health: ${info.health}% (${info.status})`);
390    console.log(`Fed: ${info.fed ? "Yes" : "No"}`);
391    console.log(`Needs feeding: ${info.needsFeeding ? "Yes" : "No"}`);
392    console.log(`Number of activities: ${info.activitiesCount}`);
393
394    const recentActivities = this.getActivityLog(5);
395    if (recentActivities.length > 0) {
396      console.log(`\nRecent activities:`);
397      recentActivities.forEach((act, i) => {
398        const time = new Date(act.timestamp).toLocaleTimeString();
399        console.log(`  ${i + 1}. [${time}] ${act.activity} (health: ${act.health}%)`);
400      });
401    }
402  }
403
404  // Static method
405  static compare(dino1, dino2) {
406    console.log(`\nComparison: ${dino1.name} vs ${dino2.name}`);
407    console.log(`   Health: ${dino1.health}% vs ${dino2.health}%`);
408    console.log(`   Status: ${dino1.status} vs ${dino2.status}`);
409
410    if (dino1.health > dino2.health) {
411      console.log(`   ${dino1.name} is healthier`);
412    } else if (dino2.health > dino1.health) {
413      console.log(`   ${dino2.name} is healthier`);
414    } else {
415      console.log(`   Both have the same health`);
416    }
417  }
418}
419
420// System demonstration
421const rexy = new DinosaurManager("Rexy", "T-Rex");
422const blue = new DinosaurManager("Blue", "Velociraptor");
423
424console.log();
425console.log(rexy.feed());
426console.log(rexy.exercise());
427console.log(rexy.heal(10));
428
429console.log();
430console.log(blue.feed());
431console.log(blue.exercise());
432console.log(blue.exercise());
433
434rexy.displayReport();
435blue.displayReport();
436
437DinosaurManager.compare(rexy, blue);
438
439console.log("\nCongratulations! You've mastered object properties and methods!");

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 is Object.defineProperty() used for in JavaScript?

  2. 2. What are getters (get) and setters (set) used for in JavaScript objects?

Hands-on tasks in the game

  • Click in order

    Arrange the elements of a getter definition in an object:

Useful articles