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:
- Memory Efficiency ๐ฏ: One copy shared by all instances
- Utility Functions ๐ ๏ธ: Helper methods that donโt need instance data
- Global State ๐: Manage application-wide information
- Factory Patterns ๐ญ: Create instances in controlled ways
- Constants ๐: Define unchanging values
- 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
- ๐ฏ Use Static for Utilities: Perfect for helper functions and constants
- ๐ Shared State Carefully: Use static for truly global application state
- ๐ญ Factory Patterns: Static methods for creating instances
- ๐ Constants as Static: Define unchanging values as static readonly
- ๐ Access Control: Use private static for internal class data
- โก 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:
- ๐ป Complete the resource management system exercise above
- ๐๏ธ Refactor existing code to use static utilities where appropriate
- ๐ Explore more advanced OOP concepts in upcoming tutorials
- ๐ Build your own utility libraries with static methods!
Remember: Great applications are built with well-designed static utilities. Keep building, keep optimizing! โก
Happy coding! ๐๐๏ธโจ