Objects, Properties and Prototypes
What an object is
A value with named slots
Primitives (lesson 04) hold one thing. An object holds many, each with a name (a key) — and the bundle itself is a single value you can pass around:
const user = {
firstName: "Ada", // key: value — commas BETWEEN members, none required last
role: "admin",
isActive: true,
};
console.log(user.firstName, user["role"]); // Ada admin — dot for known keys,
// brackets when the key is in a variable
user.email = "ada@calc.dev"; // add a property at any time
delete user.isActive; // remove one
console.log(Object.keys(user)); // [ 'firstName', 'role', 'email' ]
If you get this instead: undefined where a value should be — you
read a key that does not exist; objects return undefined silently instead of
erroring. Check with Object.hasOwn(user, "email").
Methods and this
A property whose value is a function is a method. Inside a method,
this refers to the object the method was called on:
const user = {
firstName: "Ada",
describe() { // shorthand for: describe: function () {…}
return `${this.firstName} (${this.role})`;
},
role: "admin",
};
console.log(user.describe()); // Ada (admin) — this = user, because user.describe()
this is decided by the call site, and losing it is the classic bug — the
full rules get their own diagram in lesson 12 (classes).
Controlling properties
Getters, setters and freeze
const account = {
cents: 0,
get euros() { return this.cents / 100; }, // read like a property…
set euros(value) { this.cents = value * 100; }, // …but code runs underneath
};
account.euros = 12.5;
console.log(account.cents); // 1250
const settings = Object.freeze({ theme: "dark", editor: { font: "mono" } });
// settings.theme = "light"; // TypeError in strict mode (silently ignored otherwise)
settings.editor.font = "sans"; // FREEZE IS SHALLOW — nested objects stay editable
console.log(settings.editor.font); // sans
Object.freeze locks the top level only. For a fully locked structure you freeze
recursively — or, more often, you simply follow the "never mutate" discipline.
Prototypes: the invisible backup
Every object has one
When you read a property, the engine looks on the object first; if it is not there, it walks up the prototype chain — the object's backup, and the backup's backup — until it finds the name or hits the end:
A property lookup walks up the chain and stops at the first match — that is why every object "has" toString.
const greeter = {
greet() { return `hello, ${this.name}`; },
};
const ada = Object.create(greeter); // ada's prototype IS greeter
ada.name = "Ada";
console.log(ada.greet()); // hello, Ada — found one level up
console.log(Object.hasOwn(ada, "greet")); // false — inherited
console.log(Object.hasOwn(ada, "name")); // true — owned
Object.hasOwn(obj, key) asks level 1 only — use it whenever "does
this object have it" and "does the chain have it" differ. The in operator
and for...in walk the whole chain.
Prototype pollution — a real security contraexample
One line breaks every object
const victim = {};
victim.__proto__.isAdmin = true; // writes into Object.prototype — EVERYONE'S backup!
console.log(({}).isAdmin); // true — a brand-new object already "has" it
__proto__ is writable, so code that merges untrusted data into objects can
poison the shared ancestor — and every object in the program inherits the poisoned
keys. Real applications have been breached through exactly this. The defenses (run demo
foundations/21_prototype_pollution.js): validate keys (__proto__,
constructor), copy own keys only, use Map for untrusted dictionaries —
and never write to __proto__.
Plain objects as records
Updates without mutation — and spread is shallow
In practice you will mostly build plain records and update them without mutation, using spread — which, note, is also shallow:
const updated = { ...user, role: "editor" }; // a NEW object; user untouched
console.log(updated.role, user.role); // editor admin
Practice: model a record
The task
Run foundations/15_objects_lab.js with node, predicting each output.
Then build a todo object with a done getter, freeze it, and try to
break it — explain which level froze.
The checklist
- You can add, read, and delete properties, and test ownership with
Object.hasOwn. - You can explain what
thisis inside a method — and that the call site decides. - You can trace a property lookup through the prototype chain on the diagram.
- You can explain prototype pollution in two sentences and name two defenses.