javascript
exercises
exercises.js⚡javascript
/**
* ========================================
* 7.5 PROTOTYPES - EXERCISES
* ========================================
* Difficulty: ⭐ = Easy, ⭐⭐ = Medium, ⭐⭐⭐ = Hard
*/
/**
* EXERCISE 1: Basic Prototype Chain ⭐
*
* Create an object `animal` with properties `type: "animal"` and
* a method `eat()` that logs "Eating...".
* Create an object `dog` that inherits from `animal` and add
* an `own` property `breed: "Labrador"`.
* Return an object with:
* - dog: the dog object
* - canEat: boolean, whether dog has access to eat method
* - ownBreed: boolean, whether breed is own property
*/
function exercise1() {
// Your code here
}
// console.log(exercise1());
// Expected: { dog: {...}, canEat: true, ownBreed: true }
/**
* EXERCISE 2: Prototype Chain Length ⭐
*
* Given an object, count how many objects are in its prototype chain
* (not including null but including Object.prototype).
* For example, a plain object {} has 1 (just Object.prototype).
*/
function exercise2(obj) {
// Your code here
}
// console.log(exercise2({})); // 1
// console.log(exercise2(Object.create({}))); // 2
// console.log(exercise2(Object.create(null))); // 0
/**
* EXERCISE 3: Object.create with Properties ⭐⭐
*
* Create a function that creates a "Point" object using Object.create().
* The prototype should have:
* - `distanceFromOrigin()` method: returns sqrt(x² + y²)
* - `toString()` method: returns "(x, y)"
* Use property descriptors to make x and y enumerable but not writable.
*/
function exercise3(x, y) {
// Your code here
}
// const point = exercise3(3, 4);
// console.log(point.distanceFromOrigin()); // 5
// console.log(point.toString()); // "(3, 4)"
// point.x = 10; // Should fail silently or throw in strict mode
// console.log(point.x); // Still 3
/**
* EXERCISE 4: Constructor Function ⭐⭐
*
* Create a constructor function `Counter` that:
* - Takes an initial count value (default 0)
* - Has methods on prototype: increment(), decrement(), getCount()
* - increment/decrement should return `this` for chaining
*/
function Counter(initial = 0) {
// Your code here
}
// Add prototype methods here
// const counter = new Counter(5);
// console.log(counter.increment().increment().getCount()); // 7
// console.log(counter.decrement().getCount()); // 6
/**
* EXERCISE 5: Inheritance with Constructors ⭐⭐
*
* Create a `Shape` constructor with:
* - Property: name
* - Prototype method: describe() returns `"This is a ${name}"`
*
* Create a `Circle` constructor that:
* - Inherits from Shape
* - Has additional property: radius
* - Has method: area() returns π * radius²
* - Overrides describe() to return `"This is a circle with radius ${radius}"`
*/
function Shape(name) {
// Your code here
}
function Circle(radius) {
// Your code here
}
// Set up inheritance here
// const circle = new Circle(5);
// console.log(circle.describe()); // "This is a circle with radius 5"
// console.log(circle.area()); // ~78.54
// console.log(circle instanceof Circle); // true
// console.log(circle instanceof Shape); // true
/**
* EXERCISE 6: Mixin Pattern ⭐⭐
*
* Create a mixin object `Logger` with methods:
* - log(message): logs "[ClassName]: message"
* - warn(message): logs "WARNING [ClassName]: message"
* - error(message): logs "ERROR [ClassName]: message"
*
* ClassName should come from `this.constructor.name`.
* Apply this mixin to a User constructor.
*/
const Logger = {
// Your code here
};
function User(name) {
this.name = name;
}
// Apply mixin here
// const user = new User("Alice");
// user.log("logged in"); // "[User]: logged in"
// user.warn("password weak"); // "WARNING [User]: password weak"
/**
* EXERCISE 7: Get All Properties in Chain ⭐⭐
*
* Write a function that returns ALL property names in an object's
* prototype chain (own + inherited), excluding Object.prototype properties.
*/
function exercise7(obj) {
// Your code here
}
// const proto = { inherited: 1 };
// const child = Object.create(proto);
// child.own = 2;
// console.log(exercise7(child).sort()); // ["inherited", "own"]
/**
* EXERCISE 8: Property Origin ⭐⭐
*
* Write a function that, given an object and property name, returns
* which object in the chain actually has that property as its OWN property.
* Return null if property doesn't exist anywhere.
*/
function exercise8(obj, propName) {
// Your code here
}
// const grandparent = { family: "Smith" };
// const parent = Object.create(grandparent);
// parent.job = "Engineer";
// const child = Object.create(parent);
// child.name = "John";
// console.log(exercise8(child, "name") === child); // true
// console.log(exercise8(child, "job") === parent); // true
// console.log(exercise8(child, "family") === grandparent); // true
// console.log(exercise8(child, "missing")); // null
/**
* EXERCISE 9: Create Dictionary Object ⭐⭐
*
* Create a function that returns a safe dictionary object (null prototype)
* with the following methods attached directly as own properties:
* - get(key): returns value or undefined
* - set(key, value): sets value, returns dictionary for chaining
* - has(key): returns boolean
* - delete(key): removes key, returns boolean
* - keys(): returns array of all keys (excluding method names)
*/
function exercise9() {
// Your code here
}
// const dict = exercise9();
// dict.set("a", 1).set("b", 2);
// console.log(dict.get("a")); // 1
// console.log(dict.has("a")); // true
// console.log(dict.keys()); // ["a", "b"]
// console.log(dict.toString); // undefined (no prototype!)
/**
* EXERCISE 10: Prototype Method Override ⭐⭐
*
* Create a base constructor `Animal` with:
* - Property: name
* - Method on prototype: speak() returns `${name} makes a sound`
*
* Create three subconstructors that override speak():
* - Dog: returns `${name} barks`
* - Cat: returns `${name} meows`
* - Cow: returns `${name} moos`
*
* Create a function `makeSpeak` that takes an array of animals
* and returns an array of their speak() outputs.
*/
function Animal(name) {
// Your code here
}
function Dog(name) {
// Your code here
}
function Cat(name) {
// Your code here
}
function Cow(name) {
// Your code here
}
function makeSpeak(animals) {
// Your code here
}
// const animals = [new Dog("Rex"), new Cat("Whiskers"), new Cow("Bessie")];
// console.log(makeSpeak(animals));
// ["Rex barks", "Whiskers meows", "Bessie moos"]
/**
* EXERCISE 11: Prototype Chain Cloner ⭐⭐⭐
*
* Write a function that deep clones an object including its prototype chain.
* The result should have the same prototype chain structure with
* cloned objects at each level.
*/
function exercise11(obj) {
// Your code here
}
// const proto1 = { a: 1 };
// const proto2 = Object.create(proto1);
// proto2.b = 2;
// const original = Object.create(proto2);
// original.c = 3;
// const cloned = exercise11(original);
// console.log(cloned.c); // 3
// console.log(cloned.b); // 2
// console.log(cloned.a); // 1
// console.log(cloned !== original); // true
// console.log(Object.getPrototypeOf(cloned) !== proto2); // true
/**
* EXERCISE 12: Prototype-based Class System ⭐⭐⭐
*
* Implement a simple `defineClass` function that:
* - Takes parent class (or null), instance methods object, static methods object
* - Returns a constructor function with proper prototype chain
* - Instance methods should include a special `_super` method to call parent
*/
function defineClass(Parent, instanceMethods, staticMethods) {
// Your code here
}
// const Vehicle = defineClass(null, {
// init(make) { this.make = make; },
// describe() { return `A ${this.make} vehicle`; }
// }, {
// category: "transport"
// });
// const Car = defineClass(Vehicle, {
// init(make, model) {
// this._super("init", make);
// this.model = model;
// },
// describe() {
// return `${this._super("describe")} - Model: ${this.model}`;
// }
// }, {});
// const myCar = new Car("Toyota", "Camry");
// console.log(myCar.describe()); // "A Toyota vehicle - Model: Camry"
// console.log(Vehicle.category); // "transport"
/**
* EXERCISE 13: Property Delegation Pattern ⭐⭐⭐
*
* Create a `delegate` function that makes one object delegate
* specified properties to another object. When the property is
* accessed on the delegator, it should look it up on the delegate.
* When set on delegator, it should set on delegate.
*/
function delegate(delegator, delegatee, properties) {
// Your code here
}
// const settings = { theme: "dark", fontSize: 14 };
// const user = { name: "Alice" };
// delegate(user, settings, ["theme", "fontSize"]);
// console.log(user.theme); // "dark"
// user.fontSize = 16;
// console.log(settings.fontSize); // 16
/**
* EXERCISE 14: Observable Prototype ⭐⭐⭐
*
* Create a function that wraps an object such that any property
* access or modification triggers a callback. The wrapper should
* maintain the prototype chain of the original object.
*/
function makeObservable(obj, callback) {
// Your code here
}
// const proto = { greet() { return "Hello"; } };
// const original = Object.create(proto);
// original.name = "Alice";
// const log = [];
// const observed = makeObservable(original, (type, prop, value) => {
// log.push({ type, prop, value });
// });
// observed.name;
// observed.age = 30;
// observed.greet();
// console.log(log);
// [{ type: "get", prop: "name", value: "Alice" },
// { type: "set", prop: "age", value: 30 },
// { type: "get", prop: "greet", value: [Function] }]
/**
* EXERCISE 15: Safe Prototype Extension ⭐⭐⭐
*
* Create a function `extendPrototypeSafely` that adds methods to
* a constructor's prototype but:
* - Only adds if method doesn't already exist
* - Makes added methods non-enumerable
* - Keeps track of added methods for potential rollback
* Returns an object with:
* - added: array of method names that were added
* - skipped: array of method names that already existed
* - rollback(): function to remove all added methods
*/
function extendPrototypeSafely(Constructor, methods) {
// Your code here
}
// function MyClass() {}
// MyClass.prototype.existing = function() {};
// const result = extendPrototypeSafely(MyClass, {
// existing: function() { return "new"; },
// newMethod: function() { return "added"; }
// });
// console.log(result.added); // ["newMethod"]
// console.log(result.skipped); // ["existing"]
// const instance = new MyClass();
// console.log(instance.newMethod()); // "added"
// result.rollback();
// console.log(instance.newMethod); // undefined
// ============================================
// SOLUTIONS (Hidden below - try first!)
// ============================================
/*
// SOLUTION 1:
function exercise1() {
const animal = {
type: "animal",
eat() {
console.log("Eating...");
}
};
const dog = Object.create(animal);
dog.breed = "Labrador";
return {
dog,
canEat: typeof dog.eat === "function",
ownBreed: Object.hasOwn(dog, "breed")
};
}
// SOLUTION 2:
function exercise2(obj) {
let count = 0;
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
count++;
proto = Object.getPrototypeOf(proto);
}
return count;
}
// SOLUTION 3:
function exercise3(x, y) {
const pointProto = {
distanceFromOrigin() {
return Math.sqrt(this.x ** 2 + this.y ** 2);
},
toString() {
return `(${this.x}, ${this.y})`;
}
};
return Object.create(pointProto, {
x: { value: x, writable: false, enumerable: true, configurable: false },
y: { value: y, writable: false, enumerable: true, configurable: false }
});
}
// SOLUTION 4:
function Counter(initial = 0) {
this.count = initial;
}
Counter.prototype.increment = function() {
this.count++;
return this;
};
Counter.prototype.decrement = function() {
this.count--;
return this;
};
Counter.prototype.getCount = function() {
return this.count;
};
// SOLUTION 5:
function Shape(name) {
this.name = name;
}
Shape.prototype.describe = function() {
return `This is a ${this.name}`;
};
function Circle(radius) {
Shape.call(this, "circle");
this.radius = radius;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
Circle.prototype.area = function() {
return Math.PI * this.radius ** 2;
};
Circle.prototype.describe = function() {
return `This is a circle with radius ${this.radius}`;
};
// SOLUTION 6:
const Logger = {
log(message) {
console.log(`[${this.constructor.name}]: ${message}`);
},
warn(message) {
console.log(`WARNING [${this.constructor.name}]: ${message}`);
},
error(message) {
console.log(`ERROR [${this.constructor.name}]: ${message}`);
}
};
function User(name) {
this.name = name;
}
Object.assign(User.prototype, Logger);
// SOLUTION 7:
function exercise7(obj) {
const properties = new Set();
let current = obj;
while (current !== null && current !== Object.prototype) {
for (const prop of Object.getOwnPropertyNames(current)) {
properties.add(prop);
}
current = Object.getPrototypeOf(current);
}
return [...properties];
}
// SOLUTION 8:
function exercise8(obj, propName) {
let current = obj;
while (current !== null) {
if (Object.hasOwn(current, propName)) {
return current;
}
current = Object.getPrototypeOf(current);
}
return null;
}
// SOLUTION 9:
function exercise9() {
const dict = Object.create(null);
const methodNames = ["get", "set", "has", "delete", "keys"];
dict.get = function(key) {
return this[key];
};
dict.set = function(key, value) {
this[key] = value;
return this;
};
dict.has = function(key) {
return key in this;
};
dict.delete = function(key) {
if (key in this) {
delete this[key];
return true;
}
return false;
};
dict.keys = function() {
return Object.keys(this).filter(k => !methodNames.includes(k));
};
return dict;
}
// SOLUTION 10:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
return `${this.name} barks`;
};
function Cat(name) {
Animal.call(this, name);
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.speak = function() {
return `${this.name} meows`;
};
function Cow(name) {
Animal.call(this, name);
}
Cow.prototype = Object.create(Animal.prototype);
Cow.prototype.constructor = Cow;
Cow.prototype.speak = function() {
return `${this.name} moos`;
};
function makeSpeak(animals) {
return animals.map(animal => animal.speak());
}
// SOLUTION 11:
function exercise11(obj) {
if (obj === null) return null;
const proto = Object.getPrototypeOf(obj);
const clonedProto = proto === Object.prototype ? proto : exercise11(proto);
const clone = Object.create(clonedProto);
for (const key of Object.getOwnPropertyNames(obj)) {
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor.value && typeof descriptor.value === "object") {
descriptor.value = structuredClone(descriptor.value);
}
Object.defineProperty(clone, key, descriptor);
}
return clone;
}
// SOLUTION 12:
function defineClass(Parent, instanceMethods, staticMethods) {
function Class(...args) {
if (instanceMethods.init) {
instanceMethods.init.apply(this, args);
}
}
if (Parent) {
Class.prototype = Object.create(Parent.prototype);
Class.prototype.constructor = Class;
}
Class.prototype._super = function(methodName, ...args) {
const parentProto = Parent ? Parent.prototype : Object.prototype;
if (typeof parentProto[methodName] === "function") {
return parentProto[methodName].apply(this, args);
}
};
for (const key in instanceMethods) {
if (key !== "init") {
Class.prototype[key] = instanceMethods[key];
}
}
for (const key in staticMethods) {
Class[key] = staticMethods[key];
}
return Class;
}
// SOLUTION 13:
function delegate(delegator, delegatee, properties) {
for (const prop of properties) {
Object.defineProperty(delegator, prop, {
get() {
return delegatee[prop];
},
set(value) {
delegatee[prop] = value;
},
enumerable: true,
configurable: true
});
}
}
// SOLUTION 14:
function makeObservable(obj, callback) {
const proto = Object.getPrototypeOf(obj);
return new Proxy(obj, {
get(target, prop, receiver) {
let value;
if (Object.hasOwn(target, prop)) {
value = target[prop];
} else {
value = Reflect.get(target, prop, receiver);
}
callback("get", prop, value);
return value;
},
set(target, prop, value, receiver) {
callback("set", prop, value);
return Reflect.set(target, prop, value, receiver);
},
getPrototypeOf() {
return proto;
}
});
}
// SOLUTION 15:
function extendPrototypeSafely(Constructor, methods) {
const added = [];
const skipped = [];
for (const name in methods) {
if (name in Constructor.prototype) {
skipped.push(name);
} else {
Object.defineProperty(Constructor.prototype, name, {
value: methods[name],
writable: true,
enumerable: false,
configurable: true
});
added.push(name);
}
}
return {
added,
skipped,
rollback() {
for (const name of added) {
delete Constructor.prototype[name];
}
}
};
}
*/
console.log('========================================');
console.log('Prototype Exercises Loaded!');
console.log('Uncomment the test code to check your solutions.');
console.log('========================================');