In Jurassic Park, every piece of equipment has specific parameters - some are read-only (like the model serial number), some change dynamically (like the current battery level), and some are calculated based on other values. JavaScript objects can model exactly this: read-only properties, computed properties, hidden internals with controlled access.
Every object property has a descriptor - a set of attributes that control how the property behaves:
1const sensor = { value: 42 };
2
3// Get property descriptor
4const descriptor = Object.getOwnPropertyDescriptor(sensor, 'value');
5console.log(descriptor);
6// {
7// value: 42,
8// writable: true, // can be changed
9// enumerable: true, // shows up in for...in
10// configurable: true // can be deleted/reconfigured
11// }Object.defineProperty lets you precisely control a property:1const dino = { name: 'Rex' };
2
3// Read-only property (writable: false)
4Object.defineProperty(dino, 'id', {
5 value: 'DINO-001',
6 writable: false, // cannot be changed
7 enumerable: true,
8 configurable: false // cannot be deleted
9});
10
11console.log(dino.id); // "DINO-001"
12dino.id = 'OTHER'; // silently fails (or throws in strict mode)
13console.log(dino.id); // "DINO-001" - unchanged
14
15// Attempt to delete won't work either
16delete dino.id; // returns false
17console.log(dino.id); // still "DINO-001"You can define multiple properties at once with
Object.defineProperties: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); // 61const dino = { name: 'Rex' };
2
3// Hidden property (enumerable: false)
4Object.defineProperty(dino, '_internalCode', {
5 value: 'SECRET-XYZ',
6 writable: true,
7 enumerable: false, // won't show up in for...in or Object.keys
8 configurable: false
9});
10
11console.log(Object.keys(dino)); // ['name', 'id'] - _internalCode hidden!
12console.log(dino._internalCode); // "SECRET-XYZ" - still accessible directlyGetters and setters let you control property access - execute code when reading or writing:
1class DinosaurHealth {
2 constructor(name) {
3 this.name = name;
4 this._health = 100; // _health is the private backing store
5 this._hunger = 0;
6 }
7
8 // Getter - called when you read .health
9 get health() {
10 return this._health;
11 }
12
13 // Setter - called when you write .health = value
14 set health(value) {
15 if (value < 0) value = 0;
16 if (value > 100) value = 100;
17 this._health = value;
18
19 if (value < 20) {
20 console.log(`WARNING: ${this.name} critically ill!`);
21 }
22 }
23
24 // Computed getter
25 get status() {
26 if (this._health > 80) return 'Excellent';
27 if (this._health > 50) return 'Good';
28 if (this._health > 20) return 'Poor';
29 return 'Critical';
30 }
31
32 get hunger() {
33 return this._hunger;
34 }
35
36 set hunger(value) {
37 this._hunger = Math.max(0, Math.min(100, value));
38 }
39}
40
41const rex = new DinosaurHealth('Rex');
42rex.health = 150; // setter clamps to 100
43console.log(rex.health); // 100
44console.log(rex.status); // "Excellent"
45
46rex.health = 15; // WARNING: Rex critically ill!
47console.log(rex.status); // "Critical"You 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 // Assuming value has 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"The
this keyword in methods refers to the object the method is called on. However, in some cases (e.g., when passing a method as a callback) we can lose the this context. The bind method allows explicit setting of the this value.1const rex = {
2 name: "Rex",
3 makeSound: function() {
4 console.log(`${this.name} roars loudly!`);
5 }
6};
7
8// Normal usage
9rex.makeSound(); // "Rex roars loudly!"
10
11// Problematic usage - lost this context
12const rexSound = rex.makeSound;
13// rexSound(); // Error: this.name is undefined
14
15// Solution - use bind to set the this value
16const boundRexSound = rex.makeSound.bind(rex);
17boundRexSound(); // "Rex roars loudly!"
18
19// We can also bind this to a different object
20const blue = { name: "Blue" };
21const blueSound = rex.makeSound.bind(blue);
22blueSound(); // "Blue roars loudly!"call and apply let you invoke a function with a specific 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// call with explicitly passed arguments
9attack.call(trex, "Triceratops", 50);
10// "Tyrannosaurus Rex attacks Triceratops and deals 50 damage!"
11
12// apply with arguments in an array
13attack.apply(raptor, ["Gallimimus", 30]);
14// "Velociraptor attacks Gallimimus and deals 30 damage!"These methods lock down object mutability at different levels:
1// preventExtensions - no new properties, but existing ones can change
2const openDino = { name: 'Rex', health: 100 };
3Object.preventExtensions(openDino);
4
5openDino.health = 90; // OK
6openDino.newProp = 'x'; // silently ignored
7console.log(openDino.newProp); // undefined
8
9// seal - no new properties, no deletions, but values can change
10const sealedDino = { name: 'Blue', health: 100 };
11Object.seal(sealedDino);
12
13sealedDino.health = 80; // OK - value can change
14sealedDino.zone = 'A'; // ignored - can't add
15delete sealedDino.name; // ignored - can't delete
16console.log(sealedDino); // { name: 'Blue', health: 80 }
17
18// freeze - completely immutable (shallow!)
19const frozenDino = Object.freeze({ name: 'Stego', stats: { health: 100 } });
20frozenDino.name = 'Other'; // ignored
21frozenDino.stats.health = 50; // WORKS! freeze is shallow
22console.log(frozenDino.name); // "Stego" - protected
23console.log(frozenDino.stats.health); // 50 - nested object NOT frozen!Symbols as property keys are unique and hidden from standard enumeration:
1const SECRET_ID = Symbol('secretId');
2const VERSION = Symbol('version');
3
4const dino = {
5 name: 'Rex',
6 [SECRET_ID]: 'DINO-007',
7 [VERSION]: '2.0'
8};
9
10console.log(dino.name); // "Rex" - regular property
11console.log(dino[SECRET_ID]); // "DINO-007" - symbol property
12console.log(Object.keys(dino)); // ['name'] - symbols not listed!
13
14// Getting symbol keys
15const symbolKeys = Object.getOwnPropertySymbols(dino);
16console.log(symbolKeys); // [Symbol(secretId), Symbol(version)]JavaScript has built-in symbols that customize object behavior:
1class DinosaurCollection {
2 constructor() {
3 this.dinos = ['Rex', 'Blue', 'Delta'];
4 }
5
6 // Make the object iterable with for...of
7 [Symbol.iterator]() {
8 let index = 0;
9 const dinos = this.dinos;
10 return {
11 next() {
12 if (index < dinos.length) {
13 return { value: dinos[index++], done: false };
14 }
15 return { value: undefined, done: true };
16 }
17 };
18 }
19
20 // Customize string representation
21 get [Symbol.toStringTag]() {
22 return 'DinosaurCollection';
23 }
24}
25
26const collection = new DinosaurCollection();
27for (const dino of collection) {
28 console.log(dino); // "Rex", "Blue", "Delta"
29}
30
31console.log([...collection]); // ["Rex", "Blue", "Delta"]Proxy lets you intercept and customize fundamental object operations:1const dinoData = {
2 name: 'Rex',
3 health: 100,
4 zone: 'A'
5};
6
7const monitoredDino = new Proxy(dinoData, {
8 get(target, prop) {
9 console.log(`Reading: ${prop}`);
10 return target[prop];
11 },
12
13 set(target, prop, value) {
14 console.log(`Setting ${prop} = ${value} (was: ${target[prop]})`);
15 if (prop === 'health' && (value < 0 || value > 100)) {
16 throw new RangeError(`health must be 0-100, got ${value}`);
17 }
18 target[prop] = value;
19 return true;
20 }
21});
22
23console.log(monitoredDino.name); // "Reading: name" then "Rex"
24monitoredDino.health = 85; // "Setting health = 85 (was: 100)"
25// monitoredDino.health = 200; // RangeError!1class DinosaurHealthMonitor {
2 constructor(dinoData) {
3 this._data = { ...dinoData };
4 this._log = [];
5
6 // Use Proxy to intercept all property changes
7 return new Proxy(this, {
8 set(target, prop, value) {
9 if (prop.startsWith('_')) {
10 // Private properties - set directly
11 target[prop] = value;
12 return true;
13 }
14
15 const old = target._data[prop];
16 target._data[prop] = value;
17 target._log.push({
18 prop,
19 from: old,
20 to: value,
21 time: new Date().toISOString()
22 });
23 return true;
24 },
25
26 get(target, prop) {
27 if (prop.startsWith('_') || typeof target[prop] === 'function') {
28 return target[prop];
29 }
30 return target._data[prop];
31 }
32 });
33 }
34
35 getLog() {
36 return this._log;
37 }
38
39 getReport() {
40 return { ...this._data };
41 }
42}
43
44const monitor = new DinosaurHealthMonitor({
45 name: 'Rex',
46 health: 100,
47 hunger: 0
48});
49
50monitor.health = 85;
51monitor.hunger = 40;
52monitor.health = 70;
53
54console.log(monitor.getLog());
55// [{ prop: 'health', from: 100, to: 85, ... }, ...]
56console.log(monitor.getReport());
57// { name: 'Rex', health: 70, hunger: 40 }In this lesson we explored advanced properties and methods of JavaScript objects:
These techniques are essential when building complex systems like our dinosaur health monitoring system in Jurassic Park. They enable more reliable, secure, and elegant programming solutions.
"Object properties in JavaScript are like the control panel in an enclosure" - says Dr. Rex. "Some switches are locked (read-only), some have warning lights (setters), some are behind glass (non-enumerable). Proxy is like a camera that records every button press!"