javascript

examples

examples.js
/**
 * ========================================
 * 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('========================================');
Examples - JavaScript Tutorial | DeepML