+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 30 of 355

๐Ÿ› ๏ธ Static Members and Methods: Class-Level Functionality

Master TypeScript static members and methods to create utility functions, manage shared state, and implement powerful design patterns ๐Ÿš€

๐Ÿš€Intermediate
30 min read

Prerequisites

  • Understanding of TypeScript classes and access modifiers ๐Ÿ—๏ธ
  • Knowledge of object-oriented programming concepts ๐Ÿ“š
  • Familiarity with design patterns ๐ŸŽฏ

What you'll learn

  • Master static properties and methods in TypeScript ๐ŸŽฏ
  • Implement utility classes and helper functions ๐Ÿ› ๏ธ
  • Create powerful design patterns with static members โœจ
  • Manage shared state and global functionality ๐Ÿš€

๐ŸŽฏ Introduction

Welcome to the realm of TypeScript static members! ๐Ÿ›๏ธ Think of static members as the โ€œclass-level superpowersโ€ that belong to the class itself, not to individual instances. Theyโ€™re like the shared utilities in a toolshed that everyone can use! ๐Ÿงฐ

Static members are perfect for creating utility functions, managing shared state, implementing design patterns like Singleton, and providing class-level functionality that doesnโ€™t depend on instance data. Theyโ€™re your go-to solution when you need functionality thatโ€™s related to a class but doesnโ€™t need an object instance! โšก

By the end of this tutorial, youโ€™ll be wielding static members like a pro, creating elegant, efficient code that maximizes reusability and minimizes overhead! ๐Ÿง™โ€โ™‚๏ธ

Letโ€™s explore the power of class-level functionality! ๐Ÿš€

๐Ÿ“š Understanding Static Members

๐Ÿค” What are Static Members?

Static members belong to the class itself, not to individual instances:

  • Static Properties ๐Ÿ“Š: Shared data across all instances
  • Static Methods โšก: Functions that operate at the class level
  • Static Blocks ๐Ÿ”ง: Initialization code for static members
  • Static Getters/Setters ๐Ÿ”: Controlled access to static data

Think of static members like:

  • ๐Ÿ›๏ธ Library Rules: Apply to the library, not individual books
  • ๐ŸŒ Global Constants: Mathematical constants like ฯ€
  • ๐Ÿญ Factory Workers: Create objects without being objects themselves
  • ๐Ÿ“Š Shared Counters: Track totals across all instances

๐Ÿ’ก Why Use Static Members?

Hereโ€™s why static members are powerful:

  1. Memory Efficiency ๐ŸŽฏ: One copy shared by all instances
  2. Utility Functions ๐Ÿ› ๏ธ: Helper methods that donโ€™t need instance data
  3. Global State ๐ŸŒ: Manage application-wide information
  4. Factory Patterns ๐Ÿญ: Create instances in controlled ways
  5. Constants ๐Ÿ“–: Define unchanging values
  6. Counters & Tracking ๐Ÿ“Š: Monitor class-wide statistics

๐Ÿ”ง Basic Static Syntax

๐Ÿ“ Static Properties and Methods

Letโ€™s start with the fundamental static syntax:

// ๐ŸŽฎ Game Statistics with static members
class GameStats {
  // ๐Ÿ“Š Static properties - shared across all instances
  static totalGamesPlayed: number = 0;
  static totalPlayersOnline: number = 0;
  static readonly maxPlayersAllowed: number = 1000;
  static readonly gameVersion: string = "2.1.0";
  
  // ๐Ÿ”’ Private static for internal tracking
  private static gameStartTime: Date = new Date();
  private static highScores: number[] = [];
  
  // ๐ŸŒ Instance properties
  public playerName: string;
  public currentScore: number;
  private sessionStartTime: Date;
  
  constructor(playerName: string) {
    this.playerName = playerName;
    this.currentScore = 0;
    this.sessionStartTime = new Date();
    
    // ๐Ÿ“ˆ Update static counters when new player joins
    GameStats.totalPlayersOnline++;
    console.log(`๐ŸŽฎ ${playerName} joined the game! Players online: ${GameStats.totalPlayersOnline}`);
  }
  
  // โšก Static methods - operate at class level
  static startNewGame(): void {
    GameStats.totalGamesPlayed++;
    GameStats.gameStartTime = new Date();
    console.log(`๐Ÿš€ New game started! Total games played: ${GameStats.totalGamesPlayed}`);
  }
  
  static getGameInfo(): string {
    const uptime = Date.now() - GameStats.gameStartTime.getTime();
    const uptimeHours = Math.floor(uptime / (1000 * 60 * 60));
    
    return `
๐ŸŽฎ Game Information:
๐Ÿ“Š Version: ${GameStats.gameVersion}
๐Ÿ‘ฅ Players Online: ${GameStats.totalPlayersOnline}/${GameStats.maxPlayersAllowed}
๐ŸŽฏ Total Games: ${GameStats.totalGamesPlayed}
โฐ Server Uptime: ${uptimeHours} hours
    `.trim();
  }
  
  static addHighScore(score: number): void {
    GameStats.highScores.push(score);
    GameStats.highScores.sort((a, b) => b - a); // Sort descending
    GameStats.highScores = GameStats.highScores.slice(0, 10); // Keep top 10
    console.log(`๐Ÿ† New high score recorded: ${score}`);
  }
  
  static getTopScores(): readonly number[] {
    return [...GameStats.highScores]; // Return copy
  }
  
  static canJoinGame(): boolean {
    return GameStats.totalPlayersOnline < GameStats.maxPlayersAllowed;
  }
  
  // ๐Ÿ“Š Static method to get statistics
  static getStatistics(): {
    totalGames: number;
    playersOnline: number;
    averageScore: number;
    serverUptimeHours: number;
  } {
    const uptime = Date.now() - GameStats.gameStartTime.getTime();
    const uptimeHours = Math.floor(uptime / (1000 * 60 * 60));
    const averageScore = GameStats.highScores.length > 0 
      ? GameStats.highScores.reduce((sum, score) => sum + score, 0) / GameStats.highScores.length
      : 0;
    
    return {
      totalGames: GameStats.totalGamesPlayed,
      playersOnline: GameStats.totalPlayersOnline,
      averageScore: Math.round(averageScore),
      serverUptimeHours: uptimeHours
    };
  }
  
  // ๐ŸŒ Instance methods
  updateScore(points: number): void {
    this.currentScore += points;
    console.log(`โญ ${this.playerName} scored ${points} points! Total: ${this.currentScore}`);
  }
  
  endSession(): void {
    // ๐Ÿ“ˆ Update static high scores
    GameStats.addHighScore(this.currentScore);
    
    // ๐Ÿ“‰ Update static counters
    GameStats.totalPlayersOnline--;
    
    const sessionLength = Date.now() - this.sessionStartTime.getTime();
    const sessionMinutes = Math.floor(sessionLength / (1000 * 60));
    
    console.log(`๐Ÿ‘‹ ${this.playerName} ended session. Score: ${this.currentScore}, Duration: ${sessionMinutes} minutes`);
  }
  
  // ๐Ÿ“Š Instance method that uses static data
  getPlayerInfo(): string {
    return `
๐Ÿ‘ค Player: ${this.playerName}
๐ŸŽฏ Current Score: ${this.currentScore}
๐Ÿ† Personal Best: ${Math.max(this.currentScore, ...GameStats.getTopScores().slice(0, 1))}
๐ŸŽฎ Game Version: ${GameStats.gameVersion}
    `.trim();
  }
}

// ๐Ÿš€ Test static members
console.log("๐ŸŽฎ Testing Static Members:\n");

// โœ… Access static members without creating instances
console.log(GameStats.getGameInfo());
GameStats.startNewGame();

// โœ… Check if game can accept new players
console.log(`Can join game: ${GameStats.canJoinGame()}`);

// Create players (instances)
const alice = new GameStats("Alice");
const bob = new GameStats("Bob");

// Instance operations
alice.updateScore(100);
bob.updateScore(250);
alice.updateScore(150);

// End sessions (updates static data)
alice.endSession();
bob.endSession();

// โœ… Access static data
console.log("\n๐Ÿ“Š Final Statistics:");
console.log(GameStats.getGameInfo());
console.log(`๐Ÿ† Top Scores: ${GameStats.getTopScores().join(", ")}`);

const stats = GameStats.getStatistics();
console.log(`๐Ÿ“ˆ Average Score: ${stats.averageScore}`);

๐Ÿญ Static Factory Patterns

๐ŸŽฏ Factory Methods for Object Creation

Static methods are perfect for creating specialized factory patterns:

// ๐ŸŒˆ Color class with static factory methods
class Color {
  private r: number;
  private g: number;
  private b: number;
  private a: number;
  
  // ๐Ÿ“– Static color constants
  static readonly RED = new Color(255, 0, 0);
  static readonly GREEN = new Color(0, 255, 0);
  static readonly BLUE = new Color(0, 0, 255);
  static readonly WHITE = new Color(255, 255, 255);
  static readonly BLACK = new Color(0, 0, 0);
  static readonly TRANSPARENT = new Color(0, 0, 0, 0);
  
  // ๐Ÿ“Š Static registry of named colors
  private static namedColors: Map<string, Color> = new Map([
    ["red", Color.RED],
    ["green", Color.GREEN],
    ["blue", Color.BLUE],
    ["white", Color.WHITE],
    ["black", Color.BLACK],
    ["transparent", Color.TRANSPARENT]
  ]);
  
  constructor(r: number, g: number, b: number, a: number = 1) {
    this.r = this.clamp(r, 0, 255);
    this.g = this.clamp(g, 0, 255);
    this.b = this.clamp(b, 0, 255);
    this.a = this.clamp(a, 0, 1);
  }
  
  // ๐Ÿ”ง Private helper method
  private clamp(value: number, min: number, max: number): number {
    return Math.max(min, Math.min(max, value));
  }
  
  // ๐Ÿญ Static factory methods for different color creation patterns
  
  // ๐ŸŽจ Create from hex string
  static fromHex(hex: string): Color {
    // Remove # if present
    hex = hex.replace('#', '');
    
    if (hex.length === 3) {
      // Short hex format (e.g., "abc" -> "aabbcc")
      hex = hex.split('').map(char => char + char).join('');
    }
    
    if (hex.length !== 6) {
      throw new Error(`โŒ Invalid hex color: ${hex}`);
    }
    
    const r = parseInt(hex.substr(0, 2), 16);
    const g = parseInt(hex.substr(2, 2), 16);
    const b = parseInt(hex.substr(4, 2), 16);
    
    console.log(`๐ŸŽจ Created color from hex: #${hex}`);
    return new Color(r, g, b);
  }
  
  // ๐ŸŒˆ Create from HSL values
  static fromHSL(h: number, s: number, l: number): Color {
    h = h / 360;
    s = s / 100;
    l = l / 100;
    
    const hue2rgb = (p: number, q: number, t: number): number => {
      if (t < 0) t += 1;
      if (t > 1) t -= 1;
      if (t < 1/6) return p + (q - p) * 6 * t;
      if (t < 1/2) return q;
      if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
      return p;
    };
    
    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    const p = 2 * l - q;
    
    const r = Math.round(hue2rgb(p, q, h + 1/3) * 255);
    const g = Math.round(hue2rgb(p, q, h) * 255);
    const b = Math.round(hue2rgb(p, q, h - 1/3) * 255);
    
    console.log(`๐ŸŒˆ Created color from HSL: (${h*360}, ${s*100}%, ${l*100}%)`);
    return new Color(r, g, b);
  }
  
  // ๐ŸŽฏ Create from named color
  static fromName(name: string): Color | null {
    const color = Color.namedColors.get(name.toLowerCase());
    if (color) {
      console.log(`๐Ÿ“› Created color from name: ${name}`);
      return new Color(color.r, color.g, color.b, color.a);
    }
    console.log(`โŒ Unknown color name: ${name}`);
    return null;
  }
  
  // ๐ŸŽฒ Create random color
  static random(): Color {
    const r = Math.floor(Math.random() * 256);
    const g = Math.floor(Math.random() * 256);
    const b = Math.floor(Math.random() * 256);
    
    console.log(`๐ŸŽฒ Created random color: rgb(${r}, ${g}, ${b})`);
    return new Color(r, g, b);
  }
  
  // ๐Ÿ”„ Create from grayscale value
  static grayscale(value: number): Color {
    const gray = Math.round(value * 255);
    console.log(`โšซ Created grayscale color: ${Math.round(value * 100)}%`);
    return new Color(gray, gray, gray);
  }
  
  // ๐ŸŒˆ Create color palette
  static createPalette(baseColor: Color, count: number): Color[] {
    const palette: Color[] = [];
    const [h, s, l] = baseColor.toHSL();
    
    for (let i = 0; i < count; i++) {
      const newHue = (h + (i * 360 / count)) % 360;
      palette.push(Color.fromHSL(newHue, s, l));
    }
    
    console.log(`๐ŸŽจ Created palette with ${count} colors`);
    return palette;
  }
  
  // ๐Ÿ“ Register new named color
  static registerColor(name: string, color: Color): void {
    Color.namedColors.set(name.toLowerCase(), color);
    console.log(`๐Ÿ“ Registered new color: ${name}`);
  }
  
  // ๐Ÿ“‹ Get all named colors
  static getNamedColors(): readonly string[] {
    return Array.from(Color.namedColors.keys());
  }
  
  // ๐ŸŒ Instance methods
  toHex(): string {
    const toHex = (n: number) => Math.round(n).toString(16).padStart(2, '0');
    return `#${toHex(this.r)}${toHex(this.g)}${toHex(this.b)}`;
  }
  
  toRGB(): string {
    return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`;
  }
  
  toRGBA(): string {
    return `rgba(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)}, ${this.a})`;
  }
  
  toHSL(): [number, number, number] {
    const r = this.r / 255;
    const g = this.g / 255;
    const b = this.b / 255;
    
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    let h: number, s: number;
    const l = (max + min) / 2;
    
    if (max === min) {
      h = s = 0; // achromatic
    } else {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      
      switch (max) {
        case r: h = (g - b) / d + (g < b ? 6 : 0); break;
        case g: h = (b - r) / d + 2; break;
        case b: h = (r - g) / d + 4; break;
        default: h = 0;
      }
      h /= 6;
    }
    
    return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
  }
  
  // ๐ŸŽจ Create lighter version
  lighten(amount: number): Color {
    const [h, s, l] = this.toHSL();
    return Color.fromHSL(h, s, Math.min(100, l + amount));
  }
  
  // ๐ŸŒ™ Create darker version
  darken(amount: number): Color {
    const [h, s, l] = this.toHSL();
    return Color.fromHSL(h, s, Math.max(0, l - amount));
  }
  
  // ๐Ÿ”„ Create complementary color
  complement(): Color {
    const [h, s, l] = this.toHSL();
    const newHue = (h + 180) % 360;
    return Color.fromHSL(newHue, s, l);
  }
}

// ๐Ÿš€ Test static factory methods
console.log("\n๐ŸŽจ Testing Color Factory Methods:\n");

// Different ways to create colors
const red1 = Color.RED;                           // Static constant
const red2 = Color.fromHex("#ff0000");            // From hex
const red3 = Color.fromName("red");               // From name
const red4 = Color.fromHSL(0, 100, 50);          // From HSL

const randomColor = Color.random();               // Random color
const grayColor = Color.grayscale(0.5);          // 50% gray

console.log(`Red from hex: ${red2?.toHex()}`);
console.log(`Red from name: ${red3?.toRGB()}`);
console.log(`Red from HSL: ${red4.toRGBA()}`);
console.log(`Random color: ${randomColor.toHex()}`);
console.log(`Gray color: ${grayColor.toRGB()}`);

// Create palette
const palette = Color.createPalette(Color.BLUE, 5);
console.log("\n๐ŸŒˆ Color Palette:");
palette.forEach((color, index) => {
  console.log(`  Color ${index + 1}: ${color.toHex()}`);
});

// Register custom color
const customPurple = Color.fromHex("#8A2BE2");
Color.registerColor("custom-purple", customPurple);

console.log(`\n๐Ÿ“› Named colors: ${Color.getNamedColors().join(", ")}`);

๐Ÿ“Š Static State Management

๐ŸŒ Managing Global Application State

Static members are excellent for managing application-wide state:

// ๐Ÿข Application Configuration Manager
class AppConfig {
  // ๐Ÿ”’ Private static state
  private static instance: AppConfig | null = null;
  private static isInitialized: boolean = false;
  private static configData: Map<string, any> = new Map();
  private static watchers: Map<string, ((value: any) => void)[]> = new Map();
  
  // ๐Ÿ“– Static readonly constants
  static readonly APP_NAME: string = "TypeScript Tutorial App";
  static readonly VERSION: string = "1.0.0";
  static readonly MAX_CACHE_SIZE: number = 1000;
  
  // ๐Ÿ”’ Private constructor (Singleton pattern)
  private constructor() {
    // Private to prevent direct instantiation
  }
  
  // ๐Ÿญ Static factory method (Singleton)
  static getInstance(): AppConfig {
    if (!AppConfig.instance) {
      AppConfig.instance = new AppConfig();
      AppConfig.initialize();
    }
    return AppConfig.instance;
  }
  
  // ๐Ÿš€ Static initialization
  private static initialize(): void {
    if (AppConfig.isInitialized) return;
    
    // Set default configuration
    AppConfig.configData.set("theme", "light");
    AppConfig.configData.set("language", "en");
    AppConfig.configData.set("notifications", true);
    AppConfig.configData.set("autoSave", true);
    AppConfig.configData.set("debugMode", false);
    
    AppConfig.isInitialized = true;
    console.log(`โš™๏ธ ${AppConfig.APP_NAME} v${AppConfig.VERSION} initialized`);
  }
  
  // ๐Ÿ“ Static methods for configuration management
  static set<T>(key: string, value: T): void {
    const oldValue = AppConfig.configData.get(key);
    AppConfig.configData.set(key, value);
    
    console.log(`โš™๏ธ Config updated: ${key} = ${value}`);
    
    // Notify watchers
    AppConfig.notifyWatchers(key, value, oldValue);
  }
  
  static get<T>(key: string): T | undefined;
  static get<T>(key: string, defaultValue: T): T;
  static get<T>(key: string, defaultValue?: T): T | undefined {
    const value = AppConfig.configData.get(key);
    return value !== undefined ? value : defaultValue;
  }
  
  static has(key: string): boolean {
    return AppConfig.configData.has(key);
  }
  
  static remove(key: string): boolean {
    const existed = AppConfig.configData.delete(key);
    if (existed) {
      console.log(`๐Ÿ—‘๏ธ Config removed: ${key}`);
      AppConfig.notifyWatchers(key, undefined, undefined);
    }
    return existed;
  }
  
  // ๐Ÿ‘๏ธ Watch for configuration changes
  static watch(key: string, callback: (value: any) => void): () => void {
    if (!AppConfig.watchers.has(key)) {
      AppConfig.watchers.set(key, []);
    }
    
    AppConfig.watchers.get(key)!.push(callback);
    console.log(`๐Ÿ‘๏ธ Watcher added for: ${key}`);
    
    // Return unwatch function
    return () => {
      const watchers = AppConfig.watchers.get(key);
      if (watchers) {
        const index = watchers.indexOf(callback);
        if (index !== -1) {
          watchers.splice(index, 1);
          console.log(`๐Ÿ‘๏ธ Watcher removed for: ${key}`);
        }
      }
    };
  }
  
  // ๐Ÿ”” Notify watchers of changes
  private static notifyWatchers(key: string, newValue: any, oldValue: any): void {
    const watchers = AppConfig.watchers.get(key);
    if (watchers) {
      watchers.forEach(callback => {
        try {
          callback(newValue);
        } catch (error) {
          console.error(`โŒ Error in watcher for ${key}:`, error);
        }
      });
    }
  }
  
  // ๐Ÿ“Š Get all configuration as readonly object
  static getAllConfig(): Readonly<Record<string, any>> {
    const config: Record<string, any> = {};
    AppConfig.configData.forEach((value, key) => {
      config[key] = value;
    });
    return Object.freeze(config);
  }
  
  // ๐Ÿ’พ Export configuration
  static exportConfig(): string {
    const config = AppConfig.getAllConfig();
    return JSON.stringify(config, null, 2);
  }
  
  // ๐Ÿ“ฅ Import configuration
  static importConfig(jsonString: string): void {
    try {
      const config = JSON.parse(jsonString);
      Object.entries(config).forEach(([key, value]) => {
        AppConfig.set(key, value);
      });
      console.log("๐Ÿ“ฅ Configuration imported successfully");
    } catch (error) {
      console.error("โŒ Failed to import configuration:", error);
    }
  }
  
  // ๐Ÿ”„ Reset to defaults
  static resetToDefaults(): void {
    AppConfig.configData.clear();
    AppConfig.isInitialized = false;
    AppConfig.initialize();
    console.log("๐Ÿ”„ Configuration reset to defaults");
  }
  
  // ๐Ÿ“ˆ Get configuration statistics
  static getStats(): {
    totalKeys: number;
    totalWatchers: number;
    memoryUsage: string;
  } {
    const totalKeys = AppConfig.configData.size;
    let totalWatchers = 0;
    AppConfig.watchers.forEach(watchers => {
      totalWatchers += watchers.length;
    });
    
    // Estimate memory usage
    const configSize = JSON.stringify(AppConfig.getAllConfig()).length;
    const memoryUsage = `${Math.round(configSize / 1024 * 100) / 100} KB`;
    
    return {
      totalKeys,
      totalWatchers,
      memoryUsage
    };
  }
}

// ๐Ÿ“‹ Logger with static methods
class Logger {
  // ๐Ÿ“Š Static logging statistics
  private static logs: Array<{
    level: string;
    message: string;
    timestamp: Date;
    context?: string;
  }> = [];
  
  private static maxLogs: number = 1000;
  private static logLevel: "debug" | "info" | "warn" | "error" = "info";
  
  // ๐Ÿ“Š Static counters
  private static counters: Map<string, number> = new Map([
    ["debug", 0],
    ["info", 0],
    ["warn", 0],
    ["error", 0]
  ]);
  
  // ๐ŸŽฏ Static logging methods
  static debug(message: string, context?: string): void {
    Logger.log("debug", message, context);
  }
  
  static info(message: string, context?: string): void {
    Logger.log("info", message, context);
  }
  
  static warn(message: string, context?: string): void {
    Logger.log("warn", message, context);
  }
  
  static error(message: string, context?: string): void {
    Logger.log("error", message, context);
  }
  
  // ๐Ÿ”ง Private static logging implementation
  private static log(level: string, message: string, context?: string): void {
    const levels = ["debug", "info", "warn", "error"];
    const currentLevelIndex = levels.indexOf(Logger.logLevel);
    const messageLevelIndex = levels.indexOf(level);
    
    if (messageLevelIndex < currentLevelIndex) {
      return; // Skip logs below current level
    }
    
    const logEntry = {
      level,
      message,
      timestamp: new Date(),
      context
    };
    
    // Add to logs array
    Logger.logs.push(logEntry);
    
    // Maintain max logs limit
    if (Logger.logs.length > Logger.maxLogs) {
      Logger.logs.shift(); // Remove oldest log
    }
    
    // Update counter
    Logger.counters.set(level, (Logger.counters.get(level) || 0) + 1);
    
    // Output to console with emoji
    const emoji = { debug: "๐Ÿ›", info: "โ„น๏ธ", warn: "โš ๏ธ", error: "โŒ" }[level] || "๐Ÿ“";
    const contextStr = context ? ` [${context}]` : "";
    console.log(`${emoji} ${level.toUpperCase()}${contextStr}: ${message}`);
  }
  
  // โš™๏ธ Static configuration methods
  static setLogLevel(level: typeof Logger.logLevel): void {
    Logger.logLevel = level;
    console.log(`โš™๏ธ Log level set to: ${level}`);
  }
  
  static setMaxLogs(max: number): void {
    Logger.maxLogs = max;
    if (Logger.logs.length > max) {
      Logger.logs = Logger.logs.slice(-max);
    }
    console.log(`โš™๏ธ Max logs set to: ${max}`);
  }
  
  // ๐Ÿ“Š Get logging statistics
  static getStats(): {
    totalLogs: number;
    logCounts: Record<string, number>;
    oldestLog?: Date;
    newestLog?: Date;
  } {
    const logCounts: Record<string, number> = {};
    Logger.counters.forEach((count, level) => {
      logCounts[level] = count;
    });
    
    return {
      totalLogs: Logger.logs.length,
      logCounts,
      oldestLog: Logger.logs[0]?.timestamp,
      newestLog: Logger.logs[Logger.logs.length - 1]?.timestamp
    };
  }
  
  // ๐Ÿ“‹ Get recent logs
  static getRecentLogs(count: number = 10): readonly typeof Logger.logs {
    return Logger.logs.slice(-count);
  }
  
  // ๐Ÿงน Clear logs
  static clearLogs(): void {
    const logCount = Logger.logs.length;
    Logger.logs = [];
    Logger.counters.forEach((_, level) => {
      Logger.counters.set(level, 0);
    });
    console.log(`๐Ÿงน Cleared ${logCount} logs`);
  }
}

// ๐Ÿš€ Test static state management
console.log("\nโš™๏ธ Testing Static State Management:\n");

// Initialize app config
const appConfig = AppConfig.getInstance();

// Set up configuration watcher
const themeWatcher = AppConfig.watch("theme", (newTheme) => {
  console.log(`๐ŸŽจ Theme changed to: ${newTheme}`);
});

// Test configuration
AppConfig.set("theme", "dark");
AppConfig.set("language", "es");
AppConfig.set("maxConnections", 100);

console.log(`Current theme: ${AppConfig.get("theme")}`);
console.log(`Current language: ${AppConfig.get("language", "en")}`);

// Test logger
Logger.setLogLevel("debug");
Logger.info("Application started", "App");
Logger.debug("Debug information", "System");
Logger.warn("This is a warning", "Security");
Logger.error("An error occurred", "Database");

// Show statistics
console.log("\n๐Ÿ“Š Application Statistics:");
console.log("Config stats:", AppConfig.getStats());
console.log("Logger stats:", Logger.getStats());

// Export/import configuration
const configExport = AppConfig.exportConfig();
console.log("\n๐Ÿ’พ Exported config:", configExport);

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Confusing static and instance members

// โŒ Common confusion between static and instance
class BadCounter {
  static count: number = 0;      // Static - shared
  instanceCount: number = 0;     // Instance - per object
  
  increment() {
    // โŒ Mixing static and instance incorrectly
    this.count++;           // Error! 'count' is static, not on instance
    BadCounter.instanceCount++;  // Error! 'instanceCount' is not static
  }
}

// โœ… Clear separation of static and instance
class GoodCounter {
  static totalCount: number = 0;     // Static - shared across all
  private instanceCount: number = 0; // Instance - specific to this object
  
  constructor() {
    GoodCounter.totalCount++;       // โœ… Update static counter
  }
  
  increment(): void {
    this.instanceCount++;           // โœ… Update instance counter
    console.log(`Instance: ${this.instanceCount}, Total: ${GoodCounter.totalCount}`);
  }
  
  static getTotalCount(): number {
    return GoodCounter.totalCount;  // โœ… Static method accessing static property
  }
  
  getInstanceCount(): number {
    return this.instanceCount;      // โœ… Instance method accessing instance property
  }
}

๐Ÿคฏ Pitfall 2: Static initialization order

// โŒ Problematic static initialization
class BadInitialization {
  static value1 = BadInitialization.getValue(); // โŒ Calls method before it's defined
  static value2 = 10;
  
  static getValue(): number {
    return BadInitialization.value2 * 2; // Might be undefined!
  }
}

// โœ… Proper static initialization
class GoodInitialization {
  static readonly BASE_VALUE = 10;           // โœ… Simple values first
  static readonly COMPUTED_VALUE = GoodInitialization.computeValue(); // โœ… Then computed
  
  private static computeValue(): number {
    return GoodInitialization.BASE_VALUE * 2; // โœ… Safe to reference
  }
  
  // โœ… Or use static initialization block
  static {
    // Static initialization block - runs after all static properties
    console.log(`Computed value: ${GoodInitialization.COMPUTED_VALUE}`);
  }
}

๐Ÿ”’ Pitfall 3: Static methods accessing instance members

// โŒ Static methods trying to access instance data
class BadAccess {
  name: string = "test";
  
  static getName(): string {
    return this.name; // โŒ Error! Static context has no 'this'
  }
}

// โœ… Proper separation of concerns
class GoodAccess {
  name: string = "test";
  
  // โœ… Static method that takes instance as parameter
  static getNameFromInstance(instance: GoodAccess): string {
    return instance.name;
  }
  
  // โœ… Static method with its own data
  static getDefaultName(): string {
    return "default";
  }
  
  // โœ… Instance method that can access static data
  getFullInfo(): string {
    return `${this.name} (default: ${GoodAccess.getDefaultName()})`;
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Use Static for Utilities: Perfect for helper functions and constants
  2. ๐Ÿ“Š Shared State Carefully: Use static for truly global application state
  3. ๐Ÿญ Factory Patterns: Static methods for creating instances
  4. ๐Ÿ“– Constants as Static: Define unchanging values as static readonly
  5. ๐Ÿ”’ Access Control: Use private static for internal class data
  6. โšก Memory Efficiency: One copy of static members for all instances

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Static Resource Management System

Create a comprehensive resource management system using static members:

๐Ÿ“‹ Requirements:

  • ๐Ÿ“Š ResourceManager class with static tracking
  • ๐ŸŽฎ GameAsset class with static caching
  • ๐Ÿ“ˆ PerformanceMonitor with static metrics
  • ๐Ÿ”„ EventSystem with static event handling
  • ๐Ÿ’พ DatabaseConnection with static pooling
  • ๐ŸŽฏ ConfigurationManager with static settings

๐Ÿš€ Bonus Features:

  • Memory usage optimization
  • Resource cleanup and garbage collection
  • Performance analytics dashboard
  • Hot-reloading configuration system

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐Ÿ“Š Resource Manager with static tracking
class ResourceManager {
  // ๐Ÿ“ˆ Static tracking properties
  private static totalResourcesLoaded: number = 0;
  private static resourceCache: Map<string, any> = new Map();
  private static resourceSizes: Map<string, number> = new Map();
  private static maxCacheSize: number = 100 * 1024 * 1024; // 100MB
  private static currentCacheSize: number = 0;
  
  // ๐Ÿ“Š Resource type counters
  private static resourceCounts: Map<string, number> = new Map();
  
  // ๐Ÿš€ Static resource loading
  static async loadResource<T>(id: string, loader: () => Promise<T>): Promise<T> {
    // Check cache first
    if (ResourceManager.resourceCache.has(id)) {
      console.log(`๐Ÿ’พ Cache hit for resource: ${id}`);
      return ResourceManager.resourceCache.get(id);
    }
    
    console.log(`โฌ‡๏ธ Loading resource: ${id}`);
    const startTime = performance.now();
    
    try {
      const resource = await loader();
      const loadTime = performance.now() - startTime;
      
      // Estimate size (simplified)
      const estimatedSize = JSON.stringify(resource).length * 2; // rough estimate
      
      // Check if we have space
      if (ResourceManager.currentCacheSize + estimatedSize > ResourceManager.maxCacheSize) {
        ResourceManager.evictLRU();
      }
      
      // Cache the resource
      ResourceManager.resourceCache.set(id, resource);
      ResourceManager.resourceSizes.set(id, estimatedSize);
      ResourceManager.currentCacheSize += estimatedSize;
      ResourceManager.totalResourcesLoaded++;
      
      // Update type counter
      const type = ResourceManager.getResourceType(id);
      ResourceManager.resourceCounts.set(type, (ResourceManager.resourceCounts.get(type) || 0) + 1);
      
      console.log(`โœ… Loaded ${id} in ${loadTime.toFixed(2)}ms (${estimatedSize} bytes)`);
      return resource;
      
    } catch (error) {
      console.error(`โŒ Failed to load resource ${id}:`, error);
      throw error;
    }
  }
  
  // ๐Ÿ—‘๏ธ LRU eviction
  private static evictLRU(): void {
    // Simple LRU - remove first item (in real implementation, track access times)
    const firstKey = ResourceManager.resourceCache.keys().next().value;
    if (firstKey) {
      ResourceManager.unloadResource(firstKey);
      console.log(`๐Ÿ—‘๏ธ Evicted resource: ${firstKey}`);
    }
  }
  
  // ๐Ÿ“ค Unload resource
  static unloadResource(id: string): boolean {
    if (!ResourceManager.resourceCache.has(id)) {
      return false;
    }
    
    const size = ResourceManager.resourceSizes.get(id) || 0;
    ResourceManager.resourceCache.delete(id);
    ResourceManager.resourceSizes.delete(id);
    ResourceManager.currentCacheSize -= size;
    
    const type = ResourceManager.getResourceType(id);
    const currentCount = ResourceManager.resourceCounts.get(type) || 0;
    if (currentCount > 0) {
      ResourceManager.resourceCounts.set(type, currentCount - 1);
    }
    
    console.log(`๐Ÿ—‘๏ธ Unloaded resource: ${id}`);
    return true;
  }
  
  // ๐Ÿ” Get resource type from ID
  private static getResourceType(id: string): string {
    const extension = id.split('.').pop()?.toLowerCase() || 'unknown';
    const typeMap: Record<string, string> = {
      'png': 'image', 'jpg': 'image', 'jpeg': 'image', 'gif': 'image',
      'mp3': 'audio', 'wav': 'audio', 'ogg': 'audio',
      'mp4': 'video', 'webm': 'video',
      'json': 'data', 'xml': 'data', 'csv': 'data',
      'js': 'script', 'ts': 'script'
    };
    return typeMap[extension] || 'other';
  }
  
  // ๐Ÿ“Š Get resource statistics
  static getStats(): {
    totalLoaded: number;
    cacheSize: string;
    cacheUtilization: number;
    resourcesByType: Record<string, number>;
  } {
    const cacheUtilization = ResourceManager.maxCacheSize > 0 
      ? (ResourceManager.currentCacheSize / ResourceManager.maxCacheSize) * 100 
      : 0;
    
    const resourcesByType: Record<string, number> = {};
    ResourceManager.resourceCounts.forEach((count, type) => {
      resourcesByType[type] = count;
    });
    
    return {
      totalLoaded: ResourceManager.totalResourcesLoaded,
      cacheSize: `${(ResourceManager.currentCacheSize / 1024 / 1024).toFixed(2)} MB`,
      cacheUtilization: Math.round(cacheUtilization),
      resourcesByType
    };
  }
  
  // ๐Ÿงน Clear cache
  static clearCache(): void {
    const resourceCount = ResourceManager.resourceCache.size;
    ResourceManager.resourceCache.clear();
    ResourceManager.resourceSizes.clear();
    ResourceManager.currentCacheSize = 0;
    ResourceManager.resourceCounts.clear();
    console.log(`๐Ÿงน Cleared cache (${resourceCount} resources)`);
  }
}

// ๐ŸŽฎ Game Asset with static management
class GameAsset {
  private static assetRegistry: Map<string, GameAsset> = new Map();
  private static preloadQueue: string[] = [];
  private static loadingPromises: Map<string, Promise<GameAsset>> = new Map();
  
  public readonly id: string;
  public readonly type: string;
  public readonly url: string;
  private data: any = null;
  private isLoaded: boolean = false;
  
  constructor(id: string, type: string, url: string) {
    this.id = id;
    this.type = type;
    this.url = url;
  }
  
  // ๐Ÿญ Static factory methods
  static createTexture(id: string, url: string): GameAsset {
    return new GameAsset(id, "texture", url);
  }
  
  static createSound(id: string, url: string): GameAsset {
    return new GameAsset(id, "sound", url);
  }
  
  static createModel(id: string, url: string): GameAsset {
    return new GameAsset(id, "model", url);
  }
  
  // ๐Ÿ“ Register asset
  static register(asset: GameAsset): void {
    GameAsset.assetRegistry.set(asset.id, asset);
    console.log(`๐Ÿ“ Registered ${asset.type} asset: ${asset.id}`);
  }
  
  // ๐Ÿ” Get asset by ID
  static get(id: string): GameAsset | undefined {
    return GameAsset.assetRegistry.get(id);
  }
  
  // โฌ‡๏ธ Load asset
  static async load(id: string): Promise<GameAsset> {
    const asset = GameAsset.assetRegistry.get(id);
    if (!asset) {
      throw new Error(`โŒ Asset not found: ${id}`);
    }
    
    // Return existing loading promise if in progress
    if (GameAsset.loadingPromises.has(id)) {
      return GameAsset.loadingPromises.get(id)!;
    }
    
    // Return immediately if already loaded
    if (asset.isLoaded) {
      return asset;
    }
    
    // Start loading
    const loadingPromise = ResourceManager.loadResource(id, async () => {
      // Simulate asset loading based on type
      const mockData = await GameAsset.simulateAssetLoad(asset.type, asset.url);
      asset.data = mockData;
      asset.isLoaded = true;
      return asset;
    });
    
    GameAsset.loadingPromises.set(id, loadingPromise);
    
    try {
      const loadedAsset = await loadingPromise;
      GameAsset.loadingPromises.delete(id);
      return loadedAsset;
    } catch (error) {
      GameAsset.loadingPromises.delete(id);
      throw error;
    }
  }
  
  // ๐ŸŽญ Simulate asset loading
  private static async simulateAssetLoad(type: string, url: string): Promise<any> {
    // Simulate different loading times for different asset types
    const loadTimes = { texture: 100, sound: 200, model: 500 };
    const loadTime = loadTimes[type as keyof typeof loadTimes] || 100;
    
    await new Promise(resolve => setTimeout(resolve, loadTime));
    
    return {
      type,
      url,
      size: Math.floor(Math.random() * 1000000) + 10000, // Random size
      loadedAt: new Date()
    };
  }
  
  // ๐Ÿ“‹ Preload assets
  static preload(assetIds: string[]): void {
    GameAsset.preloadQueue.push(...assetIds);
    console.log(`๐Ÿ“‹ Added ${assetIds.length} assets to preload queue`);
  }
  
  // ๐Ÿš€ Process preload queue
  static async processPreloadQueue(): Promise<void> {
    console.log(`๐Ÿš€ Processing preload queue (${GameAsset.preloadQueue.length} assets)`);
    
    const loadPromises = GameAsset.preloadQueue.map(id => 
      GameAsset.load(id).catch(error => {
        console.error(`โŒ Failed to preload ${id}:`, error);
        return null;
      })
    );
    
    const results = await Promise.all(loadPromises);
    const successCount = results.filter(result => result !== null).length;
    
    GameAsset.preloadQueue = [];
    console.log(`โœ… Preloaded ${successCount} assets`);
  }
  
  // ๐Ÿ“Š Get asset statistics
  static getAssetStats(): {
    totalRegistered: number;
    totalLoaded: number;
    loadingInProgress: number;
    assetsByType: Record<string, number>;
  } {
    let totalLoaded = 0;
    const assetsByType: Record<string, number> = {};
    
    GameAsset.assetRegistry.forEach(asset => {
      if (asset.isLoaded) totalLoaded++;
      
      const type = asset.type;
      assetsByType[type] = (assetsByType[type] || 0) + 1;
    });
    
    return {
      totalRegistered: GameAsset.assetRegistry.size,
      totalLoaded,
      loadingInProgress: GameAsset.loadingPromises.size,
      assetsByType
    };
  }
}

// ๐Ÿ“ˆ Performance Monitor
class PerformanceMonitor {
  private static metrics: Map<string, number[]> = new Map();
  private static startTimes: Map<string, number> = new Map();
  private static counters: Map<string, number> = new Map();
  
  // โฑ๏ธ Start timing
  static startTimer(name: string): void {
    PerformanceMonitor.startTimes.set(name, performance.now());
  }
  
  // โน๏ธ End timing
  static endTimer(name: string): number {
    const startTime = PerformanceMonitor.startTimes.get(name);
    if (!startTime) {
      console.warn(`โš ๏ธ No start time found for timer: ${name}`);
      return 0;
    }
    
    const duration = performance.now() - startTime;
    PerformanceMonitor.startTimes.delete(name);
    
    // Store metric
    if (!PerformanceMonitor.metrics.has(name)) {
      PerformanceMonitor.metrics.set(name, []);
    }
    
    const metrics = PerformanceMonitor.metrics.get(name)!;
    metrics.push(duration);
    
    // Keep only last 100 measurements
    if (metrics.length > 100) {
      metrics.shift();
    }
    
    console.log(`โฑ๏ธ ${name}: ${duration.toFixed(2)}ms`);
    return duration;
  }
  
  // ๐Ÿ“Š Increment counter
  static incrementCounter(name: string, amount: number = 1): void {
    const current = PerformanceMonitor.counters.get(name) || 0;
    PerformanceMonitor.counters.set(name, current + amount);
  }
  
  // ๐Ÿ“ˆ Get performance statistics
  static getStats(): Record<string, {
    count: number;
    average: number;
    min: number;
    max: number;
    latest: number;
  }> {
    const stats: Record<string, any> = {};
    
    PerformanceMonitor.metrics.forEach((values, name) => {
      if (values.length === 0) return;
      
      const sum = values.reduce((a, b) => a + b, 0);
      stats[name] = {
        count: values.length,
        average: sum / values.length,
        min: Math.min(...values),
        max: Math.max(...values),
        latest: values[values.length - 1]
      };
    });
    
    return stats;
  }
  
  // ๐Ÿ“Š Get counter values
  static getCounters(): Record<string, number> {
    const counters: Record<string, number> = {};
    PerformanceMonitor.counters.forEach((value, key) => {
      counters[key] = value;
    });
    return counters;
  }
  
  // ๐Ÿงน Clear all metrics
  static clearMetrics(): void {
    PerformanceMonitor.metrics.clear();
    PerformanceMonitor.counters.clear();
    PerformanceMonitor.startTimes.clear();
    console.log("๐Ÿงน Performance metrics cleared");
  }
}

// ๐Ÿš€ Demo the resource management system
console.log("\n๐Ÿ“Š Testing Resource Management System:\n");

// Register some game assets
const texture1 = GameAsset.createTexture("player_sprite", "assets/player.png");
const texture2 = GameAsset.createTexture("enemy_sprite", "assets/enemy.png");
const sound1 = GameAsset.createSound("bgm", "assets/background.mp3");
const sound2 = GameAsset.createSound("sfx_jump", "assets/jump.wav");

GameAsset.register(texture1);
GameAsset.register(texture2);
GameAsset.register(sound1);
GameAsset.register(sound2);

// Test performance monitoring
PerformanceMonitor.startTimer("asset_loading");

// Load assets
const loadAssets = async () => {
  try {
    await GameAsset.load("player_sprite");
    await GameAsset.load("bgm");
    PerformanceMonitor.incrementCounter("assets_loaded", 2);
  } catch (error) {
    console.error("Failed to load assets:", error);
  }
};

loadAssets().then(() => {
  PerformanceMonitor.endTimer("asset_loading");
  
  // Show statistics
  console.log("\n๐Ÿ“Š Resource Statistics:");
  console.log(ResourceManager.getStats());
  
  console.log("\n๐ŸŽฎ Asset Statistics:");
  console.log(GameAsset.getAssetStats());
  
  console.log("\n๐Ÿ“ˆ Performance Statistics:");
  console.log(PerformanceMonitor.getStats());
  console.log("Counters:", PerformanceMonitor.getCounters());
});

// Test preloading
GameAsset.preload(["enemy_sprite", "sfx_jump"]);
setTimeout(() => {
  GameAsset.processPreloadQueue();
}, 1000);

๐ŸŽ“ Key Takeaways

Excellent work! Youโ€™ve mastered TypeScript static members! Hereโ€™s what you can now do:

  • โœ… Create utility classes with static methods and properties ๐Ÿ› ๏ธ
  • โœ… Implement factory patterns for object creation ๐Ÿญ
  • โœ… Manage global state efficiently with static members ๐ŸŒ
  • โœ… Build powerful design patterns like Singleton and Registry ๐ŸŽฏ
  • โœ… Track application metrics with static counters and statistics ๐Ÿ“Š
  • โœ… Optimize memory usage with shared static data ๐Ÿ’พ

Remember: Static members are your tool for class-level functionality that transcends individual instances! ๐Ÿš€

๐Ÿค Next Steps

Fantastic progress! ๐ŸŽ‰ Youโ€™re now a static member expert!

Continue your TypeScript mastery journey:

  1. ๐Ÿ’ป Complete the resource management system exercise above
  2. ๐Ÿ—๏ธ Refactor existing code to use static utilities where appropriate
  3. ๐Ÿ“š Explore more advanced OOP concepts in upcoming tutorials
  4. ๐ŸŒŸ Build your own utility libraries with static methods!

Remember: Great applications are built with well-designed static utilities. Keep building, keep optimizing! โšก


Happy coding! ๐ŸŽ‰๐Ÿ›๏ธโœจ