JavaScript: Advanced
Closures
// Closure: function + lexical scope
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
counter(); // 1
counter(); // 2
// Practical: Private variables
function createCounter() {
let private = 0;
return {
increment: () => private++,
get: () => private
};
}
// Practical: Function factories
function multiplyBy(factor) {
return (num) => num * factor;
}
const double = multiplyBy(2);
double(5); // 10Scope Chain
// Global scope
const global = 'global';
function outer() {
// Function scope
const outerVar = 'outer';
function inner() {
// Inner scope
const innerVar = 'inner';
console.log(global); // ✓
console.log(outerVar); // ✓
console.log(innerVar); // ✓
}
}
// Block scope
{
let blockVar = 'block';
const blockConst = 'const';
}
// console.log(blockVar); // ✗ ReferenceErrorPrototypes
// Prototype chain
const obj = { a: 1 };
console.log(obj.toString()); // From Object.prototype
// Constructor function
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
const john = new Person('John');
john.greet();
// Prototypal inheritance
function Employee(name, role) {
Person.call(this, name);
this.role = role;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;Classes
// Class definition
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, I'm ${this.name}`;
}
// Static method
static create(name) {
return new Person(name);
}
}
// Inheritance
class Employee extends Person {
constructor(name, role) {
super(name);
this.role = role;
}
greet() {
return `${super.greet()}, I'm a ${this.role}`;
}
}
// Getters and setters
class Circle {
constructor(radius) {
this._radius = radius;
}
get area() {
return Math.PI * this._radius ** 2;
}
set radius(value) {
if (value <= 0) throw new Error('Invalid');
this._radius = value;
}
}Modules
// Export (module.js)
export const value = 42;
export function fn() {}
export class MyClass {}
export default class DefaultClass {}
// Named exports
export { value, fn, MyClass };
// Import
import DefaultClass from './module.js';
import { value, fn } from './module.js';
import * as Module from './module.js';
import Default, { named } from './module.js';
// Re-export
export { value } from './module.js';
export * from './module.js';Design Patterns
// Module Pattern
const Module = (function() {
let private = 0;
return {
public: () => private++
};
})();
// Singleton
class Singleton {
constructor() {
if (Singleton.instance) return Singleton.instance;
Singleton.instance = this;
}
}
// Observer Pattern
class EventEmitter {
constructor() { this.events = {}; }
on(event, fn) {
(this.events[event] = this.events[event] || []).push(fn);
}
emit(event, ...args) {
(this.events[event] || []).forEach(fn => fn(...args));
}
}
// Factory Pattern
function createAnimal(type) {
const animals = {
dog: { sound: 'woof' },
cat: { sound: 'meow' }
};
return { ...animals[type], type };
}Key Takeaways
- Closures enable private state and function factories
- Prototypes power JavaScript inheritance
- Classes provide cleaner syntax over prototypes
- Modules organize code into reusable units
- Design patterns solve common problems
Next Steps
→ JavaScript: Professional (Debugging, Testing, Performance)