javascript
examples
examples.js⚡javascript
/**
* ========================================
* 7.5 PROTOTYPES - CODE EXAMPLES
* ========================================
*/
/**
* EXAMPLE 1: Basic Prototype Concept
* Understanding what prototypes are
*/
console.log('--- Example 1: Basic Prototype Concept ---');
const animal = {
eats: true,
walk() {
console.log('Walking...');
},
};
const rabbit = {
jumps: true,
};
// Set animal as rabbit's prototype
Object.setPrototypeOf(rabbit, animal);
console.log(rabbit.jumps); // true (own property)
console.log(rabbit.eats); // true (inherited from animal)
rabbit.walk(); // "Walking..." (inherited method)
// Check if property is own or inherited
console.log(rabbit.hasOwnProperty('jumps')); // true
console.log(rabbit.hasOwnProperty('eats')); // false
/**
* EXAMPLE 2: The Prototype Chain
* Properties are looked up through the chain
*/
console.log('\n--- Example 2: The Prototype Chain ---');
const grandparent = { family: 'Smith', wisdom: 'Be kind' };
const parent = Object.create(grandparent);
parent.job = 'Engineer';
const child = Object.create(parent);
child.hobby = 'Gaming';
// Accessing properties through the chain
console.log(child.hobby); // "Gaming" (own)
console.log(child.job); // "Engineer" (from parent)
console.log(child.family); // "Smith" (from grandparent)
console.log(child.wisdom); // "Be kind" (from grandparent)
// Tracing the chain
console.log(Object.getPrototypeOf(child) === parent); // true
console.log(Object.getPrototypeOf(parent) === grandparent); // true
console.log(Object.getPrototypeOf(grandparent) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
/**
* EXAMPLE 3: Object.create() with Property Descriptors
* Creating objects with specific prototype and properties
*/
console.log('\n--- Example 3: Object.create() with Descriptors ---');
const personProto = {
greet() {
return `Hello, I'm ${this.name}`;
},
get displayInfo() {
return `${this.name}, age ${this.age}`;
},
};
const john = Object.create(personProto, {
name: { value: 'John', writable: true, enumerable: true },
age: { value: 30, writable: true, enumerable: true },
id: { value: 12345, writable: false, enumerable: false },
});
console.log(john.greet()); // "Hello, I'm John"
console.log(john.displayInfo); // "John, age 30"
console.log(Object.keys(john)); // ["name", "age"] (id not enumerable)
/**
* EXAMPLE 4: Object.create(null) - Dictionary Objects
* Creating objects with no prototype
*/
console.log('\n--- Example 4: Object.create(null) ---');
// Regular object has methods from Object.prototype
const regularObj = {};
console.log(regularObj.toString); // [Function: toString]
console.log(regularObj.hasOwnProperty); // [Function: hasOwnProperty]
// Null prototype object has nothing
const pureDict = Object.create(null);
pureDict.key1 = 'value1';
pureDict.key2 = 'value2';
console.log(pureDict.toString); // undefined
console.log(pureDict.hasOwnProperty); // undefined
console.log('key1' in pureDict); // true
// Safe from prototype pollution
pureDict['__proto__'] = 'malicious';
console.log(Object.getPrototypeOf(pureDict)); // null (still!)
console.log(pureDict['__proto__']); // "malicious" (just a property)
/**
* EXAMPLE 5: Constructor Functions and .prototype
* How new works with prototypes
*/
console.log('\n--- Example 5: Constructor Functions ---');
function Person(name, age) {
this.name = name;
this.age = age;
}
// Methods on prototype are shared by all instances
Person.prototype.greet = function () {
return `Hi, I'm ${this.name}`;
};
Person.prototype.birthday = function () {
this.age++;
return `Happy birthday! Now ${this.age}`;
};
const alice = new Person('Alice', 30);
const bob = new Person('Bob', 25);
console.log(alice.greet()); // "Hi, I'm Alice"
console.log(bob.greet()); // "Hi, I'm Bob"
// Methods are the same function object
console.log(alice.greet === bob.greet); // true
// Instance's prototype is constructor's prototype property
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true
/**
* EXAMPLE 6: prototype vs [[Prototype]]
* Understanding the difference
*/
console.log('\n--- Example 6: prototype vs [[Prototype]] ---');
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function () {
console.log('Woof!');
};
// Dog.prototype is what instances inherit from
const rex = new Dog('Rex');
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // true
// Dog's own [[Prototype]] is Function.prototype
console.log(Object.getPrototypeOf(Dog) === Function.prototype); // true
// Constructor reference
console.log(Dog.prototype.constructor === Dog); // true
console.log(rex.constructor === Dog); // true
/**
* EXAMPLE 7: Prototypal Inheritance Chain
* Multi-level inheritance
*/
console.log('\n--- Example 7: Prototypal Inheritance Chain ---');
// Base level
const living = {
isAlive: true,
breathe() {
console.log(`${this.name} is breathing`);
},
};
// Level 2
const animal2 = Object.create(living);
animal2.move = function () {
console.log(`${this.name} is moving`);
};
// Level 3
const mammal = Object.create(animal2);
mammal.warmBlooded = true;
mammal.nurse = function () {
console.log(`${this.name} is nursing`);
};
// Level 4
const dog = Object.create(mammal);
dog.bark = function () {
console.log(`${this.name} says woof!`);
};
// Instance
const buddy = Object.create(dog);
buddy.name = 'Buddy';
buddy.breathe(); // "Buddy is breathing"
buddy.move(); // "Buddy is moving"
buddy.nurse(); // "Buddy is nursing"
buddy.bark(); // "Buddy says woof!"
console.log(buddy.warmBlooded); // true
console.log(buddy.isAlive); // true
/**
* EXAMPLE 8: Shadowing Properties
* Own properties override prototype properties
*/
console.log('\n--- Example 8: Shadowing Properties ---');
const baseConfig = {
debug: false,
timeout: 5000,
log(msg) {
console.log(`[${this.debug ? 'DEBUG' : 'INFO'}]: ${msg}`);
},
};
const devConfig = Object.create(baseConfig);
devConfig.debug = true; // Shadows baseConfig.debug
const prodConfig = Object.create(baseConfig);
// prodConfig uses inherited debug: false
baseConfig.log.call({ debug: baseConfig.debug }, 'Base config');
devConfig.log('Dev message'); // "[DEBUG]: Dev message"
prodConfig.log('Prod message'); // "[INFO]: Prod message"
// Deleting shadow reveals inherited property
delete devConfig.debug;
console.log(devConfig.debug); // false (inherited from baseConfig)
/**
* EXAMPLE 9: isPrototypeOf() Method
* Checking prototype relationships
*/
console.log('\n--- Example 9: isPrototypeOf() ---');
const animalProto = { type: 'animal' };
const dogProto = Object.create(animalProto);
dogProto.species = 'dog';
const myDog = Object.create(dogProto);
myDog.name = 'Max';
console.log(dogProto.isPrototypeOf(myDog)); // true
console.log(animalProto.isPrototypeOf(myDog)); // true
console.log(Object.prototype.isPrototypeOf(myDog)); // true
console.log(Array.prototype.isPrototypeOf(myDog)); // false
/**
* EXAMPLE 10: instanceof Operator
* Checking constructor in prototype chain
*/
console.log('\n--- Example 10: instanceof Operator ---');
function Animal3(name) {
this.name = name;
}
function Dog3(name, breed) {
Animal3.call(this, name);
this.breed = breed;
}
// Set up inheritance
Dog3.prototype = Object.create(Animal3.prototype);
Dog3.prototype.constructor = Dog3;
const rover = new Dog3('Rover', 'Labrador');
console.log(rover instanceof Dog3); // true
console.log(rover instanceof Animal3); // true
console.log(rover instanceof Object); // true
console.log(rover instanceof Array); // false
/**
* EXAMPLE 11: Copying Methods Between Prototypes (Mixins)
* Adding capabilities from multiple sources
*/
console.log('\n--- Example 11: Prototype Mixins ---');
const canWalk = {
walk() {
console.log(`${this.name} walks`);
},
};
const canSwim = {
swim() {
console.log(`${this.name} swims`);
},
};
const canFly = {
fly() {
console.log(`${this.name} flies`);
},
};
function Duck(name) {
this.name = name;
}
// Mix in multiple capabilities
Object.assign(Duck.prototype, canWalk, canSwim, canFly);
const donald = new Duck('Donald');
donald.walk(); // "Donald walks"
donald.swim(); // "Donald swims"
donald.fly(); // "Donald flies"
/**
* EXAMPLE 12: Prototype with Getters and Setters
* Accessors in prototypes
*/
console.log('\n--- Example 12: Prototype Getters/Setters ---');
const userProto = {
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
},
};
const user1 = Object.create(userProto);
user1.firstName = 'John';
user1.lastName = 'Doe';
console.log(user1.fullName); // "John Doe"
user1.fullName = 'Jane Smith';
console.log(user1.firstName); // "Jane"
console.log(user1.lastName); // "Smith"
/**
* EXAMPLE 13: Modifying Prototypes at Runtime
* Adding methods to existing prototypes
*/
console.log('\n--- Example 13: Runtime Prototype Modification ---');
function Car(make) {
this.make = make;
}
const car1 = new Car('Toyota');
// Add method to prototype after instance creation
Car.prototype.honk = function () {
console.log(`${this.make} goes beep!`);
};
// Existing instances get the new method
car1.honk(); // "Toyota goes beep!"
const car2 = new Car('Honda');
car2.honk(); // "Honda goes beep!"
/**
* EXAMPLE 14: Checking for Own vs Inherited Properties
* Using hasOwn and hasOwnProperty
*/
console.log('\n--- Example 14: Own vs Inherited Properties ---');
const proto = { inherited: true };
const obj = Object.create(proto);
obj.own = true;
// Using Object.hasOwn (ES2022+)
console.log(Object.hasOwn(obj, 'own')); // true
console.log(Object.hasOwn(obj, 'inherited')); // false
// Using in operator (checks entire chain)
console.log('own' in obj); // true
console.log('inherited' in obj); // true
// Listing own properties only
console.log(Object.keys(obj)); // ["own"]
// Listing all enumerable properties (own + inherited)
const allProps = [];
for (const key in obj) {
allProps.push(key);
}
console.log(allProps); // ["own", "inherited"]
/**
* EXAMPLE 15: Replacing Constructor Prototype
* Complete prototype replacement
*/
console.log('\n--- Example 15: Replacing Constructor Prototype ---');
function Shape() {}
Shape.prototype.type = 'shape';
const shape1 = new Shape();
// Replace the entire prototype
Shape.prototype = {
type: 'new shape',
draw() {
console.log('Drawing');
},
};
const shape2 = new Shape();
// Old instance still references old prototype
console.log(shape1.type); // "shape"
console.log(shape1.draw); // undefined
// New instance uses new prototype
console.log(shape2.type); // "new shape"
shape2.draw(); // "Drawing"
// Note: constructor reference is lost!
console.log(shape2.constructor === Shape); // false
/**
* EXAMPLE 16: Prototype Performance Considerations
* Why method placement matters
*/
console.log('\n--- Example 16: Prototype Performance ---');
// Bad: Methods defined in constructor (new copy per instance)
function BadWidget(name) {
this.name = name;
this.render = function () {
// New function for EVERY instance
return `Rendering ${this.name}`;
};
}
// Good: Methods on prototype (shared)
function GoodWidget(name) {
this.name = name;
}
GoodWidget.prototype.render = function () {
// One function shared by all
return `Rendering ${this.name}`;
};
const bad1 = new BadWidget('A');
const bad2 = new BadWidget('B');
console.log(bad1.render === bad2.render); // false (different functions)
const good1 = new GoodWidget('A');
const good2 = new GoodWidget('B');
console.log(good1.render === good2.render); // true (same function)
/**
* EXAMPLE 17: Subclassing with Constructor Functions
* Classical inheritance pattern
*/
console.log('\n--- Example 17: Subclassing with Constructors ---');
function Vehicle(make, model) {
this.make = make;
this.model = model;
}
Vehicle.prototype.start = function () {
console.log(`${this.make} ${this.model} starting...`);
};
Vehicle.prototype.info = function () {
return `${this.make} ${this.model}`;
};
function Car4(make, model, doors) {
Vehicle.call(this, make, model); // Call parent constructor
this.doors = doors;
}
// Set up prototype chain
Car4.prototype = Object.create(Vehicle.prototype);
Car4.prototype.constructor = Car4;
// Add Car-specific methods
Car4.prototype.honk = function () {
console.log('Beep beep!');
};
// Override parent method
Car4.prototype.info = function () {
// Call parent's info
const vehicleInfo = Vehicle.prototype.info.call(this);
return `${vehicleInfo} (${this.doors} doors)`;
};
const myCar2 = new Car4('Toyota', 'Camry', 4);
myCar2.start(); // "Toyota Camry starting..."
myCar2.honk(); // "Beep beep!"
console.log(myCar2.info()); // "Toyota Camry (4 doors)"
/**
* EXAMPLE 18: Symbol.species and Subclassing Built-ins
* Controlling subclass instance creation
*/
console.log('\n--- Example 18: Symbol.species ---');
class MyArray extends Array {
// This makes methods like map return regular Array
static get [Symbol.species]() {
return Array;
}
}
const myArr = new MyArray(1, 2, 3);
console.log(myArr instanceof MyArray); // true
const mapped = myArr.map((x) => x * 2);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
/**
* EXAMPLE 19: Prototype Pollution Prevention
* Protecting against prototype attacks
*/
console.log('\n--- Example 19: Prototype Pollution Prevention ---');
// Dangerous: Using untrusted data
const userInput = JSON.parse('{"__proto__": {"polluted": true}}');
// Check before assigning
function safeAssign(target, source) {
for (const key of Object.keys(source)) {
// Skip dangerous keys
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
console.log(`Blocked dangerous key: ${key}`);
continue;
}
const value = source[key];
if (typeof value === 'object' && value !== null) {
target[key] = safeAssign({}, value);
} else {
target[key] = value;
}
}
return target;
}
const safeObj = safeAssign({}, userInput);
console.log({}.polluted); // undefined (not polluted!)
/**
* EXAMPLE 20: Complete Prototype Pattern Example
* Building a class-like system with prototypes
*/
console.log('\n--- Example 20: Complete Prototype Pattern ---');
// Helper function to create a "class"
function createClass(Parent, instanceProps, staticProps) {
function Class(...args) {
if (Parent) {
Parent.apply(this, args);
}
if (instanceProps.init) {
instanceProps.init.apply(this, args);
}
}
// Set up inheritance
if (Parent) {
Class.prototype = Object.create(Parent.prototype);
Class.prototype.constructor = Class;
}
// Add instance methods
for (const key in instanceProps) {
if (key !== 'init') {
Class.prototype[key] = instanceProps[key];
}
}
// Add static methods
for (const key in staticProps) {
Class[key] = staticProps[key];
}
return Class;
}
// Create base class
const Animal4 = createClass(
null,
{
init(name) {
this.name = name;
},
speak() {
console.log(`${this.name} makes a sound`);
},
},
{
kingdom: 'Animalia',
}
);
// Create subclass
const Dog4 = createClass(
Animal4,
{
init(name, breed) {
this.breed = breed;
},
speak() {
console.log(`${this.name} barks`);
},
fetch() {
console.log(`${this.name} fetches the ball`);
},
},
{
domesticated: true,
}
);
const dog4 = new Dog4('Max', 'Golden Retriever');
dog4.speak(); // "Max barks"
dog4.fetch(); // "Max fetches the ball"
console.log(dog4.name); // "Max"
console.log(dog4.breed); // "Golden Retriever"
console.log(Dog4.domesticated); // true
console.log(Animal4.kingdom); // "Animalia"
console.log('\n========================================');
console.log('End of Prototype Examples');
console.log('========================================');