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
- Belong to the Class: Called directly through the class name, no instance needed
- Cannot Access Instance Properties: Cannot use
thisto access instance properties in static methods - Can Access Static Properties: Can access static properties and other static methods of the class
- Memory Efficiency: Can be used without creating instances, saving memory
Basic Syntax
ES6 Static Methods
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 functionES5 Static Methods
// 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")); // trueApplication Scenarios
1. Utility Class Methods
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
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
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
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(); // TypeErrorStatic Methods in Inheritance
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
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
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
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
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:
- Definition: Use the
statickeyword to define static methods - Calling: Call directly through the class name, cannot be called through instances
- Access Restrictions: Cannot access instance properties, can access static properties and methods
- Application Scenarios: Utility class methods, factory methods, configuration management, singleton pattern, etc.
- Inheritance: Child classes can inherit parent class static methods
- 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.