Warm-up · Activity 1 of 7
Warm-up from lesson B5.3. What does this print?
const base = {a: 1, b: 2};
const copy = {...base, b: 3};
console.log(copy.a, copy.b, base.b);// I2.1 · ~33 min · Intermediate
After this lesson you can say where JavaScript finds a property: on the object itself or further up its prototype chain. You build objects on a shared prototype with Object.create.
Lesson 1 of 5 in I2 Objects, prototypes and classes
You will be able to
Warm-up · Activity 1 of 7
const base = {a: 1, b: 2};
const copy = {...base, b: 3};
console.log(copy.a, copy.b, base.b);Predict · Activity 2 of 7
const proto = {greet() { return "hello"; }};
const obj = Object.create(proto);
console.log(Object.keys(obj).length, obj.greet());Practice · Activity 3 of 7
const animal = {speak() { return "..."; }};
const cat = Object.____(animal);Practice · Activity 4 of 7
Practice · Activity 5 of 7
const proto = {color: "red"};
const a = Object.create(proto);
const b = Object.create(proto);
a.color = "blue";
console.log(a.color, b.color, proto.color);Brain teaser · Activity 6 of 7
const proto = {shared: 1};
const obj = Object.create(proto);
obj.own = 2;
const seen = [];
for (const key in obj) seen.push(key);
console.log(seen.join(","), Object.keys(obj).join(","));Apply · Activity 7 of 7
Check your work against this list
Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.
Worked example
animal holds a default sound and a speak() method. cat and fish are made with Object.create(animal), so both link to it. cat gets its own sound, which shadows the default; fish keeps the inherited one. The program then checks the links and the kinds of property, and follows the chain to its end.
main.js
// A prototype with a shared method.
const animal = {
sound: "...",
speak() {
return `${this.name} says ${this.sound}`;
}
};
// Two objects that link to it.
const cat = Object.create(animal);
cat.name = "Mia";
cat.sound = "meow"; // an own property: it shadows animal.sound
const fish = Object.create(animal);
fish.name = "Bubbles";
console.log(cat.speak());
console.log(fish.speak());
console.log(Object.getPrototypeOf(cat) === animal);
console.log(Object.hasOwn(cat, "sound"), Object.hasOwn(fish, "sound"));
console.log("speak" in fish, Object.hasOwn(fish, "speak"));
console.log(cat.speak === fish.speak);
console.log(Object.keys(cat));
// The chain goes on: animal links to Object.prototype, which links to null.
console.log(Object.getPrototypeOf(animal) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));
Run it with
node main.jsOutput
Mia says meow
Bubbles says ...
true
true false
true false
true
[ 'name', 'sound' ]
true
nullTab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.
Exercise 1 of 2
Write describe(obj, key). It returns "own" when key is an own property of obj, "inherited" when obj only gets key from its prototype chain, and "missing" when the key is not found at all. The starter uses only the in operator, so it calls inherited properties own.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.
Run the tests: inherited keys come back as own. Which check is true only for the object itself?
Object.hasOwn(obj, key) is true for own properties only; key in obj is true for own and inherited ones.
Check Object.hasOwn first and return "own", then key in obj and return "inherited", else "missing".
One way to solve it. Yours can look different and still pass the checks.
export function describe(obj, key) {
if (Object.hasOwn(obj, key)) return "own";
if (key in obj) return "inherited";
return "missing";
}
Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.js
export function describe(obj, key) {
if (key in obj) return "own";
return "missing";
}
main.test.js
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {describe} from './main.js';
test('A property on the object itself is own', () => {
const got = describe({name: 'Ada'}, 'name');
assert.equal(got, 'own', `describe({name}, 'name') gave ${got}`);
});
test('A property from the prototype is inherited', () => {
const child = Object.create({kind: 'fruit'});
const got = describe(child, 'kind');
assert.equal(got, 'inherited', `describe(child, 'kind') gave ${got}`);
});
test('toString on a plain object is inherited', () => {
const got = describe({}, 'toString');
assert.equal(got, 'inherited', `describe({}, 'toString') gave ${got}`);
});
test('An unknown key is missing', () => {
const got = describe({}, 'colour');
assert.equal(got, 'missing', `describe({}, 'colour') gave ${got}`);
});
package.json
{
"type": "module"
}
package.json tells Node.js that the .js files are modules; keep it in the folder.
Run the program:
node main.jsRun the checks (needs learnrun.js in the same folder):
node --testDownload learnrun.jsExercise 2 of 2
makeCounter(start) should return a counter whose increment() adds one to its count and returns it. The method must live once, on counterProto, and not on every counter. The starter gives each counter its own copy of increment. Build the counter with Object.create(counterProto) and give it only an own count.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.
Run the tests: increment works, but it is an own property of every counter. Where should it live instead?
Object.create(counterProto) gives an empty object that inherits increment.
const counter = Object.create(counterProto); counter.count = start; return counter;
One way to solve it. Yours can look different and still pass the checks.
export const counterProto = {
increment() {
this.count += 1;
return this.count;
}
};
export function makeCounter(start) {
const counter = Object.create(counterProto);
counter.count = start;
return counter;
}
Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.js
export const counterProto = {
increment() {
this.count += 1;
return this.count;
}
};
export function makeCounter(start) {
return {
count: start,
increment() {
this.count += 1;
return this.count;
}
};
}
main.test.js
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {counterProto, makeCounter} from './main.js';
test('increment adds one and returns the new count', () => {
const counter = makeCounter(5);
counter.increment();
const got = counter.increment();
assert.equal(got, 7, `two increments from 5 gave ${got}`);
});
test('The prototype of a counter is counterProto', () => {
const counter = makeCounter(0);
assert.equal(Object.getPrototypeOf(counter) === counterProto, true, 'Object.getPrototypeOf(counter) should be counterProto');
});
test('increment is inherited, not an own property', () => {
const counter = makeCounter(0);
assert.equal(Object.hasOwn(counter, 'increment'), false, 'each counter has its own increment; it should come from counterProto');
});
test('Two counters keep their own counts', () => {
const a = makeCounter(0);
const b = makeCounter(10);
a.increment();
assert.deepEqual([a.count, b.count], [1, 10], `the counts are ${a.count} and ${b.count}`);
});
package.json
{
"type": "module"
}
package.json tells Node.js that the .js files are modules; keep it in the folder.
Run the program:
node main.jsRun the checks (needs learnrun.js in the same folder):
node --testDownload learnrun.jsconst animal = {speak() { return "..."; }};
const cat = Object.create();
console.log(cat.speak());
What Node.js prints
TypeError: Object prototype may only be an Object or null: undefinedWhy, and the fix
Object.create needs the object to link to as its first argument; it does not copy or guess one. Pass the prototype: Object.create(animal). When you really want an object without any prototype, say so with Object.create(null).
const stock = Object.create(null);
stock.apples = 3;
console.log(stock.hasOwnProperty("apples"));
What Node.js prints
TypeError: stock.hasOwnProperty is not a functionWhy, and the fix
hasOwnProperty is inherited from Object.prototype. An object made with Object.create(null) has no prototype, so it has no hasOwnProperty either. Use the static method instead, which works for every object: Object.hasOwn(stock, "apples").
const proto = {
hello: () => `I am ${this.name}`
};
const a = Object.create(proto);
a.name = "Ada";
console.log(a.hello());
What Node.js prints
TypeError: Cannot read properties of undefined (reading 'name')Why, and the fix
An arrow function has no this of its own; it takes this from the code around it. At the top of a module that is undefined, so this.name fails, whichever object called hello. Write shared methods with the method syntax, hello() { return `I am ${this.name}`; }, so this is the object before the dot.
JavaScript in the browser: your browser’s own engine, in a sandboxed worker. Syntax errors are located with acorn 8.18.0, MIT. Licence and source
5 questions, no hints. Score 80% or more to complete the lesson.
Finish every activity above to unlock the exit ticket.