// a. Working with Prototypical Inheritance and Classes
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function () {
  console.log(`${this.name} makes a noise.`);
};

function Dog(name) {
  Animal.call(this, name);
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.speak = function () {
  console.log(`${this.name} barks.`);
};

const dog1 = new Dog("Buddy");
dog1.speak(); // Output: Buddy barks.

//classes

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}

const dog2 = new Dog("Max");
dog2.speak(); // Output: Max barks.

 
// b. Working with Object and Array Destructuring
const user = { name: 'Alice', age: 25, location: 'New York' };
const { name, age } = user;
console.log(`${name} is ${age} years old.`);
 
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(`First: ${first}, Second: ${second}, Rest: ${rest}`);
 
// c. Working with Modules
// In another file (mathUtils.js), create:
// export function add(a, b) { return a + b; }
// export function multiply(a, b) { return a * b; }
// Then import and use in this file:
// import { add, multiply } from './mathUtils.js';
 
// d. Working with Function Generators and Symbols
function* numberGenerator() {
    let num = 1;
    while (true) {
        yield num++;
    }
}
const gen = numberGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
 
const uniqueID = Symbol('id');
const obj = { [uniqueID]: 12345 };
console.log(obj[uniqueID]);
 
// e. Working with Closures
function outerFunction() {
  let count = 0;

  function innerFunction() {
    count++;
    console.log(count);
  }

  return innerFunction;
}

const counter = outerFunction();
counter(); // 1
counter(); // 2
counter(); // 3