JavaScript and TypeScript course Β· Module 2: Functions, Arrays and Objects
Object Properties and Methods
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); // 6Getters 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); // 0We 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)); // trueObject.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)); // trueObject.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)); // falseThe 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:
- Property descriptors - give you precise control over the attributes of properties
- Getters and setters - let you run code when a property is read or written
- Binding this - the bind, call and apply methods help you control the context of a function
- Symbols - provide unique identifiers for object properties
- Freezing objects - the freeze, seal and preventExtensions methods let you limit how far objects can be modified
- 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. What is Object.defineProperty() used for in JavaScript?
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: