Skip to content

JavaScript Static Methods

Static methods are methods that belong to the class itself rather than to class instances. They cannot be accessed through class instances and can only be called through the class itself. Static methods are very useful in utility classes, factory methods, configuration management, and other scenarios. In this chapter, we'll learn about static methods in JavaScript.

What are Static Methods

Static methods are methods defined directly on the class rather than on the class prototype. They are independent of class instances and are typically used for operations related to the class that don't require instantiation.

Characteristics of Static Methods

  1. Belong to the Class: Called directly through the class name, no instance needed
  2. Cannot Access Instance Properties: Cannot use this to access instance properties in static methods
  3. Can Access Static Properties: Can access static properties and other static methods of the class
  4. Memory Efficiency: Can be used without creating instances, saving memory

Basic Syntax

ES6 Static Methods

javascript
class MathUtils {
  // Static method
  static add(a, b) {
    return a + b;
  }
  
  static multiply(a, b) {
    return a * b;
  }
  
  static power(base, exponent) {
    return Math.pow(base, exponent);
  }
  
  // Static property (ES2022)
  static PI = 3.14159;
  
  // Instance method
  calculateArea(radius) {
    return MathUtils.PI * radius * radius; // Access static property
  }
  
  // Instance method
  calculateCircumference(radius) {
    return 2 * MathUtils.PI * radius;
  }
}

// Call static methods
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.multiply(4, 6)); // 24
console.log(MathUtils.power(2, 3)); // 8
console.log(MathUtils.PI); // 3.14159

// Create instance and call instance methods
const utils = new MathUtils();
console.log(utils.calculateArea(5)); // 78.53975
console.log(utils.calculateCircumference(5)); // 31.4159

// Error: Cannot call static method through instance
// console.log(utils.add(1, 2)); // TypeError: utils.add is not a function

ES5 Static Methods

javascript
// Define static methods in ES5
function StringUtils() {
  // Constructor
}

// Static method
StringUtils.capitalize = function(str) {
  if (!str) return str;
  return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
};

StringUtils.reverse = function(str) {
  if (!str) return str;
  return str.split("").reverse().join("");
};

StringUtils.isPalindrome = function(str) {
  if (!str) return false;
  const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, "");
  return cleaned === cleaned.split("").reverse().join("");
};

// Use static methods
console.log(StringUtils.capitalize("hello world")); // "Hello world"
console.log(StringUtils.reverse("hello")); // "olleh"
console.log(StringUtils.isPalindrome("A man a plan a canal Panama")); // true

Application Scenarios

1. Utility Class Methods

javascript
class DateUtils {
  // Format date
  static formatDate(date, format = "YYYY-MM-DD") {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    
    return format
      .replace("YYYY", year)
      .replace("MM", month)
      .replace("DD", day);
  }
  
  // Calculate days between two dates
  static daysBetween(date1, date2) {
    const oneDay = 24 * 60 * 60 * 1000;
    const firstDate = new Date(date1);
    const secondDate = new Date(date2);
    
    return Math.round(Math.abs((firstDate - secondDate) / oneDay));
  }
  
  // Get current timestamp
  static now() {
    return Date.now();
  }
  
  // Validate date format
  static isValidDate(dateString) {
    const date = new Date(dateString);
    return date !== "Invalid Date" && !isNaN(date);
  }
  
  // Get days in month
  static getDaysInMonth(year, month) {
    return new Date(year, month, 0).getDate();
  }
}

// Usage examples
const today = new Date();
console.log(DateUtils.formatDate(today)); // "2024-01-15"
console.log(DateUtils.formatDate(today, "DD/MM/YYYY")); // "15/01/2024"

const date1 = new Date("2024-01-01");
const date2 = new Date("2024-01-15");
console.log(DateUtils.daysBetween(date1, date2)); // 14

console.log(DateUtils.isValidDate("2024-01-15")); // true
console.log(DateUtils.isValidDate("invalid date")); // false
console.log(DateUtils.getDaysInMonth(2024, 2)); // 29 (2024 is a leap year)

2. Factory Methods

javascript
class User {
  constructor(id, name, email, role = "user") {
    this.id = id;
    this.name = name;
    this.email = email;
    this.role = role;
    this.createdAt = new Date();
  }
  
  // Static factory method
  static createAdmin(name, email) {
    const id = this.generateId();
    return new User(id, name, email, "admin");
  }
  
  static createCustomer(name, email) {
    const id = this.generateId();
    return new User(id, name, email, "customer");
  }
  
  static createGuest() {
    const id = this.generateId();
    return new User(id, "Guest", "guest@example.com", "guest");
  }
  
  // Private static method (generate unique ID)
  static generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
  }
  
  // Static method: Create user from JSON
  static fromJSON(jsonString) {
    const data = JSON.parse(jsonString);
    return new User(data.id, data.name, data.email, data.role);
  }
  
  // Static method: Validate user data
  static validate(userData) {
    const errors = [];
    
    if (!userData.name || userData.name.trim().length === 0) {
      errors.push("Name cannot be empty");
    }
    
    if (!userData.email || !userData.email.includes("@")) {
      errors.push("Email format is incorrect");
    }
    
    return {
      valid: errors.length === 0,
      errors: errors
    };
  }
  
  getInfo() {
    return {
      id: this.id,
      name: this.name,
      email: this.email,
      role: this.role,
      createdAt: this.createdAt
    };
  }
  
  toJSON() {
    return JSON.stringify(this.getInfo());
  }
}

// Use factory methods to create different types of users
const admin = User.createAdmin("Admin", "admin@example.com");
const customer = User.createCustomer("Customer", "customer@example.com");
const guest = User.createGuest();

console.log(admin.getInfo());
console.log(customer.getInfo());
console.log(guest.getInfo());

// Use fromJSON method
const userJson = '{"id":"123","name":"John","email":"john@example.com","role":"user"}';
const userFromJson = User.fromJSON(userJson);
console.log(userFromJson.getInfo());

// Validate user data
const validationResult = User.validate({ name: "John", email: "john@example.com" });
console.log(validationResult); // { valid: true, errors: [] }

const invalidResult = User.validate({ name: "", email: "invalid" });
console.log(invalidResult); // { valid: false, errors: ["Name cannot be empty", "Email format is incorrect"] }

3. Configuration Management

javascript
class Config {
  // Static property to store config
  static #config = new Map();
  
  // Default config
  static #defaults = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retries: 3,
    debug: false
  };
  
  // Set config item
  static set(key, value) {
    this.#config.set(key, value);
  }
  
  // Get config item
  static get(key) {
    if (this.#config.has(key)) {
      return this.#config.get(key);
    }
    
    if (key in this.#defaults) {
      return this.#defaults[key];
    }
    
    return undefined;
  }
  
  // Batch set config
  static setBatch(configObject) {
    Object.entries(configObject).forEach(([key, value]) => {
      this.set(key, value);
    });
  }
  
  // Get all config
  static getAll() {
    const result = { ...this.#defaults };
    
    for (const [key, value] of this.#config) {
      result[key] = value;
    }
    
    return result;
  }
  
  // Reset config
  static reset() {
    this.#config.clear();
  }
  
  // Export config
  static export() {
    return JSON.stringify(this.getAll(), null, 2);
  }
  
  // Import config
  static import(configString) {
    try {
      const config = JSON.parse(configString);
      this.setBatch(config);
      return true;
    } catch (error) {
      console.error("Failed to import config:", error);
      return false;
    }
  }
}

// Usage examples
console.log(Config.get("apiUrl")); // "https://api.example.com" (default)
console.log(Config.get("timeout")); // 5000 (default)

Config.set("apiUrl", "https://custom-api.example.com");
Config.set("customField", "custom value");

console.log(Config.get("apiUrl")); // "https://custom-api.example.com"
console.log(Config.get("customField")); // "custom value"

console.log("All config:", Config.getAll());

// Export and import config
const exportedConfig = Config.export();
console.log("Exported config:", exportedConfig);

Config.reset();
console.log("Config after reset:", Config.getAll());

Config.import(exportedConfig);
console.log("Config after import:", Config.getAll());

Static vs Instance Methods

Access Permissions

javascript
class Example {
  constructor(value) {
    this.instanceValue = value;
  }
  
  // Instance method
  instanceMethod() {
    console.log("Instance method can access:");
    console.log("- this.instanceValue:", this.instanceValue);
    console.log("- Static property:", Example.staticProperty);
    console.log("- Static method:", Example.staticMethod());
  }
  
  // Static method
  static staticMethod() {
    console.log("Static method can access:");
    console.log("- Static property:", this.staticProperty);
    console.log("- Other static method:", this.anotherStaticMethod());
    // console.log(this.instanceValue); // Error: Cannot access instance property
    return "Static method return value";
  }
  
  static anotherStaticMethod() {
    return "Another static method";
  }
  
  // Static property
  static staticProperty = "Static property value";
}

const instance = new Example("Instance value");

// Call instance method
instance.instanceMethod();

// Call static method
Example.staticMethod();

// Error: Cannot call static method through instance
// instance.staticMethod(); // TypeError

Static Methods in Inheritance

javascript
class Parent {
  constructor(name) {
    this.name = name;
  }
  
  // Instance method
  greet() {
    return `Hello, I'm ${this.name}`;
  }
  
  // Static method
  static getType() {
    return "Parent";
  }
  
  static createDefault() {
    return new this("Default"); // this refers to the calling class
  }
  
  static getClassName() {
    return this.name; // this refers to the calling class
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
  
  // Override instance method
  greet() {
    return `${super.greet()}, I'm ${this.age} years old`;
  }
  
  // Override static method
  static getType() {
    return "Child";
  }
  
  // Child-specific static method
  static createTeenager(name) {
    return new this(name, 16);
  }
}

// Usage examples
const parent = new Parent("Parent Instance");
const child = new Child("Child Instance", 25);

console.log(parent.greet()); // "Hello, I'm Parent Instance"
console.log(child.greet()); // "Hello, I'm Child Instance, I'm 25 years old"

// Static method calls
console.log(Parent.getType()); // "Parent"
console.log(Child.getType()); // "Child"

// Inherited static methods
const defaultParent = Parent.createDefault();
const defaultChild = Child.createDefault();
console.log(defaultParent.greet()); // "Hello, I'm Default"
console.log(defaultChild.greet()); // "Hello, I'm Default"

console.log(Parent.getClassName()); // "Parent"
console.log(Child.getClassName()); // "Child"

// Child-specific static method
const teenager = Child.createTeenager("Teen");
console.log(teenager.greet()); // "Hello, I'm Teen, I'm 16 years old"

Best Practices

1. Singleton Pattern

javascript
class DatabaseConnection {
  constructor() {
    if (DatabaseConnection.instance) {
      return DatabaseConnection.instance;
    }
    
    this.connectionString = "default_connection_string";
    this.isConnected = false;
    
    DatabaseConnection.instance = this;
  }
  
  // Static method to get singleton instance
  static getInstance() {
    if (!DatabaseConnection.instance) {
      DatabaseConnection.instance = new DatabaseConnection();
    }
    return DatabaseConnection.instance;
  }
  
  connect(connectionString) {
    this.connectionString = connectionString || this.connectionString;
    this.isConnected = true;
    return `Connected to database: ${this.connectionString}`;
  }
  
  disconnect() {
    this.isConnected = false;
    return "Disconnected from database";
  }
  
  query(sql) {
    if (!this.isConnected) {
      throw new Error("Database not connected");
    }
    return `Executing query: ${sql}`;
  }
  
  // Static method to directly operate on singleton
  static executeQuery(sql) {
    const instance = this.getInstance();
    if (!instance.isConnected) {
      instance.connect();
    }
    return instance.query(sql);
  }
}

// Usage examples
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();

console.log(db1 === db2); // true (same instance)

console.log(db1.connect("mysql://localhost:3306/mydb"));
console.log(db2.query("SELECT * FROM users")); // Can access through db2

// Use static method
console.log(DatabaseConnection.executeQuery("SELECT * FROM products"));

2. Array Utilities

javascript
class ArrayUtils {
  // Remove duplicates
  static unique(array) {
    return [...new Set(array)];
  }
  
  // Group by
  static groupBy(array, keyFunction) {
    return array.reduce((groups, item) => {
      const key = keyFunction(item);
      if (!groups[key]) {
        groups[key] = [];
      }
      groups[key].push(item);
      return groups;
    }, {});
  }
  
  // Sort by
  static sortBy(array, key, descending = false) {
    return array.sort((a, b) => {
      const aValue = typeof key === "function" ? key(a) : a[key];
      const bValue = typeof key === "function" ? key(b) : b[key];
      
      if (aValue < bValue) return descending ? 1 : -1;
      if (aValue > bValue) return descending ? -1 : 1;
      return 0;
    });
  }
  
  // Flatten array
  static flatten(array, depth = 1) {
    if (depth <= 0) return array.slice();
    
    return array.reduce((acc, val) => {
      return acc.concat(Array.isArray(val) ? this.flatten(val, depth - 1) : val);
    }, []);
  }
  
  // Shuffle array
  static shuffle(array) {
    const result = [...array];
    for (let i = result.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [result[i], result[j]] = [result[j], result[i]];
    }
    return result;
  }
  
  // Get max value
  static max(array, keyFunction) {
    if (array.length === 0) return undefined;
    
    if (keyFunction) {
      return array.reduce((max, current) => 
        keyFunction(current) > keyFunction(max) ? current : max
      );
    }
    
    return Math.max(...array);
  }
  
  // Get min value
  static min(array, keyFunction) {
    if (array.length === 0) return undefined;
    
    if (keyFunction) {
      return array.reduce((min, current) => 
        keyFunction(current) < keyFunction(min) ? current : min
      );
    }
    
    return Math.min(...array);
  }
}

// Usage examples
const numbers = [1, 2, 2, 3, 4, 4, 5];
console.log(ArrayUtils.unique(numbers)); // [1, 2, 3, 4, 5]

const users = [
  { name: "John", age: 25, department: "IT" },
  { name: "Jane", age: 30, department: "HR" },
  { name: "Bob", age: 25, department: "IT" },
  { name: "Alice", age: 35, department: "HR" }
];

const groupedByDepartment = ArrayUtils.groupBy(users, user => user.department);
console.log(groupedByDepartment);

const sortedByAge = ArrayUtils.sortBy(users, "age");
console.log(sortedByAge);

const nestedArray = [1, [2, 3], [4, [5, 6]]];
console.log(ArrayUtils.flatten(nestedArray)); // [1, 2, 3, 4, [5, 6]]
console.log(ArrayUtils.flatten(nestedArray, 2)); // [1, 2, 3, 4, 5, 6]

console.log(ArrayUtils.max([1, 5, 3, 9, 2])); // 9
console.log(ArrayUtils.max(users, user => user.age)); // { name: "Alice", age: 35, ... }

3. Validation and Conversion Utilities

javascript
class Validator {
  // Email validation
  static isEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
  
  // Phone number validation
  static isPhoneNumber(phone) {
    const phoneRegex = /^\+?[\d\s-]{10,}$/;
    return phoneRegex.test(phone);
  }
  
  // URL validation
  static isUrl(url) {
    try {
      new URL(url);
      return true;
    } catch {
      return false;
    }
  }
  
  // Number validation
  static isNumber(value) {
    return !isNaN(parseFloat(value)) && isFinite(value);
  }
  
  // Integer validation
  static isInteger(value) {
    return Number.isInteger(Number(value));
  }
  
  // Array validation
  static isArray(value) {
    return Array.isArray(value);
  }
  
  // Object validation
  static isObject(value) {
    return value !== null && typeof value === "object" && !Array.isArray(value);
  }
  
  // Empty validation
  static isEmpty(value) {
    if (value === null || value === undefined) return true;
    if (typeof value === "string") return value.trim().length === 0;
    if (Array.isArray(value)) return value.length === 0;
    if (typeof value === "object") return Object.keys(value).length === 0;
    return false;
  }
}

class Converter {
  // Bytes to readable format
  static bytesToSize(bytes) {
    if (bytes === 0) return "0 Bytes";
    
    const k = 1024;
    const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  }
  
  // CamelCase to kebab-case
  static camelToKebab(str) {
    return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
  }
  
  // Kebab-case to camelCase
  static kebabToCamel(str) {
    return str.replace(/-([a-z])/g, match => match[1].toUpperCase());
  }
  
  // Capitalize first letter
  static capitalize(str) {
    if (!str) return str;
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
  
  // Format currency
  static formatCurrency(amount, currency = "USD") {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: currency
    }).format(amount);
  }
  
  // Format date
  static formatDate(date, locale = "en-US") {
    return new Intl.DateTimeFormat(locale).format(new Date(date));
  }
  
  // Format number
  static formatNumber(number, options = {}) {
    return new Intl.NumberFormat("en-US", options).format(number);
  }
}

// Usage examples
console.log(Validator.isEmail("test@example.com")); // true
console.log(Validator.isPhoneNumber("+1 234-567-8900")); // true
console.log(Validator.isUrl("https://example.com")); // true
console.log(Validator.isNumber("123.45")); // true
console.log(Validator.isInteger("123")); // true
console.log(Validator.isArray([1, 2, 3])); // true
console.log(Validator.isObject({ a: 1 })); // true
console.log(Validator.isEmpty("")); // true
console.log(Validator.isEmpty([])); // true
console.log(Validator.isEmpty({})); // true

console.log(Converter.bytesToSize(1024)); // "1 KB"
console.log(Converter.bytesToSize(1048576)); // "1 MB"
console.log(Converter.camelToKebab("userName")); // "user-name"
console.log(Converter.kebabToCamel("user-name")); // "userName"
console.log(Converter.capitalize("hello")); // "Hello"
console.log(Converter.formatCurrency(1234.56)); // "$1,234.56"
console.log(Converter.formatDate("2024-01-15")); // "1/15/2024"
console.log(Converter.formatNumber(1234.567, { maximumFractionDigits: 2 })); // "1,234.57"

Practical Example: Cache Manager

javascript
class CacheManager {
  // Static property to store cache data
  static #cache = new Map();
  static #timers = new Map();
  
  // Set cache
  static set(key, value, ttl = 0) {
    // Clear existing timer
    if (this.#timers.has(key)) {
      clearTimeout(this.#timers.get(key));
      this.#timers.delete(key);
    }
    
    // Set cache
    this.#cache.set(key, {
      value: value,
      createdAt: Date.now()
    });
    
    // Set expiration
    if (ttl > 0) {
      const timer = setTimeout(() => {
        this.delete(key);
      }, ttl);
      
      this.#timers.set(key, timer);
    }
    
    return true;
  }
  
  // Get cache
  static get(key) {
    if (!this.#cache.has(key)) {
      return undefined;
    }
    
    return this.#cache.get(key).value;
  }
  
  // Check if cache exists
  static has(key) {
    return this.#cache.has(key);
  }
  
  // Delete cache
  static delete(key) {
    if (this.#timers.has(key)) {
      clearTimeout(this.#timers.get(key));
      this.#timers.delete(key);
    }
    
    return this.#cache.delete(key);
  }
  
  // Clear all cache
  static clear() {
    for (const timer of this.#timers.values()) {
      clearTimeout(timer);
    }
    
    this.#timers.clear();
    this.#cache.clear();
    
    return true;
  }
  
  // Get cache info
  static getInfo(key) {
    if (!this.#cache.has(key)) {
      return null;
    }
    
    const cacheItem = this.#cache.get(key);
    return {
      key: key,
      value: cacheItem.value,
      createdAt: cacheItem.createdAt,
      age: Date.now() - cacheItem.createdAt
    };
  }
  
  // Get all cache keys
  static keys() {
    return Array.from(this.#cache.keys());
  }
  
  // Get cache size
  static size() {
    return this.#cache.size;
  }
  
  // Batch set cache
  static setBatch(items, ttl = 0) {
    Object.entries(items).forEach(([key, value]) => {
      this.set(key, value, ttl);
    });
    
    return true;
  }
  
  // Batch get cache
  static getBatch(keys) {
    const result = {};
    keys.forEach(key => {
      result[key] = this.get(key);
    });
    return result;
  }
}

// Usage examples
CacheManager.set("user:1", { name: "John", age: 25 }, 5000); // 5 seconds expiration
CacheManager.set("user:2", { name: "Jane", age: 30 });

console.log(CacheManager.get("user:1")); // { name: "John", age: 25 }
console.log(CacheManager.has("user:1")); // true
console.log(CacheManager.getInfo("user:1"));

// Batch operations
CacheManager.setBatch({
  "config:api": "https://api.example.com",
  "config:timeout": 5000
}, 10000); // 10 seconds expiration

console.log(CacheManager.getBatch(["config:api", "config:timeout"]));

console.log("Cache size:", CacheManager.size());
console.log("Cache keys:", CacheManager.keys());

// Check expired cache after 6 seconds
setTimeout(() => {
  console.log("user:1 exists:", CacheManager.has("user:1")); // false
}, 6000);

Summary

Key points of JavaScript static methods:

  1. Definition: Use the static keyword to define static methods
  2. Calling: Call directly through the class name, cannot be called through instances
  3. Access Restrictions: Cannot access instance properties, can access static properties and methods
  4. Application Scenarios: Utility class methods, factory methods, configuration management, singleton pattern, etc.
  5. Inheritance: Child classes can inherit parent class static methods
  6. Best Practices: Use static methods appropriately, avoid overuse

Static methods are an important feature of JavaScript classes. Mastering their use enables you to write more elegant and efficient code. In the next chapter, we'll learn about JavaScript arrays.

Content is for learning and research only.