Static Methods trong JavaScript

Javascript nâng cao | by Học Javascript

Trong JavaScript, phương thức tĩnh (static methods) là một loại phương thức đặc biệt trong class, được định nghĩa bằng từ khóa static. Không giống như các phương thức thông thường, phương thức tĩnh không thể được gọi từ một instance của class mà chỉ có thể được truy cập trực tiếp thông qua class chứa nó.

Phương thức tĩnh đóng vai trò quan trọng trong lập trình hướng đối tượng, giúp tổ chức mã nguồn hiệu quả hơn, đặc biệt khi cần tạo ra các tiện ích dùng chung mà không cần khởi tạo đối tượng. Chúng thường được sử dụng để xây dựng các hàm tiện ích (utility functions), xử lý logic chung hoặc tạo ra factory methods giúp khởi tạo đối tượng theo cách linh hoạt.

Trong bài viết này, chúng ta sẽ tìm hiểu chi tiết về phương thức tĩnh, cách khai báo, sự khác biệt so với phương thức thông thường, cũng như các ứng dụng thực tế và cách kế thừa phương thức tĩnh trong JavaScript.

Static methods là gì?

Phương thức tĩnh (static method) trong JavaScript là phương thức được khai báo trong class với từ khóa static. Không giống như các phương thức thông thường, phương thức tĩnh chỉ có thể được gọi trực tiếp từ class mà không thể được gọi từ một instance (đối tượng) của class.

Đặc điểm của phương thức tĩnh

  • Không thể gọi từ instance của class, chỉ có thể gọi từ chính class đó.

  • Không thể truy cập các thuộc tính hoặc phương thức không phải tĩnh của class, vì phương thức tĩnh không gắn liền với bất kỳ instance nào.

  • Thường được sử dụng để tạo các phương thức tiện ích hoặc xử lý logic chung mà không cần tạo một đối tượng cụ thể.

Ví dụ về phương thức tĩnh không thể gọi từ instance:

class Utility {
    static sayHello() {
        return "Xin chào từ phương thức tĩnh!";
    }
}

const util = new Utility();
console.log(util.sayHello()); //  Lỗi: util.sayHello is not a function
console.log(Utility.sayHello()); //  "Xin chào từ phương thức tĩnh!"

Cách khai báo phương thức tĩnh trong JavaScript

Trong JavaScript, để khai báo phương thức tĩnh, ta sử dụng từ khóa static trước tên phương thức trong class.

Cú pháp

class ClassName {
    static methodName() {
        // Thân hàm
    }
}

Ví dụ minh họa phương thức tĩnh

Ví dụ 1: Khai báo phương thức tĩnh và gọi nó từ class

class MathUtil {
    static add(a, b) {
        return a + b;
    }
}

console.log(MathUtil.add(5, 10)); //  15

Ở đây, phương thức add() được khai báo là tĩnh nên có thể gọi trực tiếp từ class MathUtil, mà không cần tạo một đối tượng.

Ví dụ 2: Phương thức tĩnh không thể truy cập thuộc tính không tĩnh

class Counter {
    constructor() {
        this.count = 0;
    }

    static increment() {
        this.count++; //  Lỗi: this.count is undefined
        return this.count;
    }
}

const counter = new Counter();
console.log(Counter.increment()); //  Lỗi

Lý do xảy ra lỗi là vì this.count không phải là một thuộc tính tĩnh, nên phương thức tĩnh increment() không thể truy cập được.

Để giải quyết vấn đề này, ta có thể sử dụng một thuộc tính tĩnh:

class Counter {
    static count = 0;

    static increment() {
        return ++this.count;
    }
}

console.log(Counter.increment()); //  1
console.log(Counter.increment()); // 2

Bây giờ count là một thuộc tính tĩnh nên có thể được truy cập bởi phương thức tĩnh increment().

Ví dụ 3: Dùng phương thức tĩnh để tạo tiện ích

Một ứng dụng phổ biến của phương thức tĩnh là viết các hàm tiện ích (utility functions) như chuyển đổi chuỗi:

class StringUtil {
    static toUpperCase(str) {
        return str.toUpperCase();
    }
}

console.log(StringUtil.toUpperCase("hello")); // "HELLO"

Ta không cần tạo một đối tượng StringUtil, chỉ cần gọi trực tiếp StringUtil.toUpperCase() từ class.

Sự khác biệt giữa phương thức tĩnh và phương thức thông thường trong JavaScript

Phương thức tĩnh (static method) và phương thức thông thường (instance method) có sự khác biệt quan trọng về cách chúng được gọi và cách chúng truy cập dữ liệu trong class.

Đặc điểm Phương thức thông thường (Instance Method) Phương thức tĩnh (Static Method)
Cách gọi Được gọi từ một instance của class Được gọi trực tiếp từ class, không cần instance
Truy cập thuộc tính Có thể truy cập vào các thuộc tính và phương thức của instance bằng this Không thể truy cập trực tiếp vào this của instance
Sử dụng Được sử dụng khi cần làm việc với dữ liệu của từng instance cụ thể Được sử dụng khi thực hiện các thao tác chung, không phụ thuộc vào instance

Phương thức thông thường

Phương thức thông thường có thể truy cập thuộc tính của một instance bằng this:

class Car {
    constructor(brand, speed) {
        this.brand = brand;
        this.speed = speed;
    }

    showDetails() {
        return `Xe ${this.brand} đang chạy với tốc độ ${this.speed} km/h.`;
    }
}

const myCar = new Car("Toyota", 120);
console.log(myCar.showDetails()); //  "Xe Toyota đang chạy với tốc độ 120 km/h."

Ở đây, showDetails() là một phương thức thông thường, nó truy cập các thuộc tính brandspeed của instance bằng this.

Phương thức tĩnh không thể truy cập this của instance

Phương thức tĩnh không thể truy cập trực tiếp các thuộc tính của instance:

class Car {
    constructor(brand, speed) {
        this.brand = brand;
        this.speed = speed;
    }

    static showDetails() {
        return `Xe ${this.brand} đang chạy với tốc độ ${this.speed} km/h.`; //  Lỗi
    }
}

const myCar = new Car("Toyota", 120);
console.log(Car.showDetails()); //  Lỗi: this.brand is undefined

Lý do lỗi là this trong phương thức tĩnh không trỏ đến một instance cụ thể.

Cách sử dụng phương thức tĩnh đúng cách

Nếu muốn phương thức tĩnh hoạt động đúng, ta không nên dùng this mà truyền dữ liệu vào dưới dạng tham số:

class Car {
    static showDetails(brand, speed) {
        return `Xe ${brand} đang chạy với tốc độ ${speed} km/h.`;
    }
}

console.log(Car.showDetails("Toyota", 120)); //  "Xe Toyota đang chạy với tốc độ 120 km/h."

Bây giờ, phương thức tĩnh showDetails() hoạt động độc lập và không phụ thuộc vào instance.

Ứng dụng thực tế của phương thức tĩnh trong JavaScript

Tạo các tiện ích (Utility Functions)

Phương thức tĩnh rất hữu ích khi viết các hàm tiện ích không phụ thuộc vào instance.

Ví dụ: Một class xử lý số học (MathUtil) cung cấp phương thức tính tổng hai số:

class MathUtil {
    static add(a, b) {
        return a + b;
    }
}

console.log(MathUtil.add(10, 20)); //  30

Ở đây, add() là phương thức tĩnh vì nó không cần lưu trạng thái của một instance nào.

Xây dựng Factory Method để tạo instance

Phương thức tĩnh có thể dùng để tạo ra các đối tượng theo mẫu thiết kế Factory Pattern.

Ví dụ: Một class User với phương thức tĩnh createGuestUser() để tạo người dùng khách (Guest):

class User {
    constructor(name, role) {
        this.name = name;
        this.role = role;
    }

    static createGuestUser() {
        return new User("Guest", "visitor");
    }
}

const guest = User.createGuestUser();
console.log(guest); // User { name: 'Guest', role: 'visitor' }

Ở đây, createGuestUser() giúp tạo một đối tượng User mặc định mà không cần chỉ định tham số.

Quản lý dữ liệu và trạng thái chung

Phương thức tĩnh có thể được dùng để quản lý dữ liệu toàn cục mà không cần tạo nhiều instance.

Ví dụ: Một class Counter giúp đếm số lần chương trình được sử dụng:

class Counter {
    static count = 0;

    static increment() {
        return ++this.count;
    }
}

console.log(Counter.increment()); //  1
console.log(Counter.increment()); //  2

Ở đây, count là một biến tĩnh dùng để theo dõi số lần increment() được gọi.

Kế thừa phương thức tĩnh trong JavaScript

Phương thức tĩnh có thể được kế thừa không?

Trong JavaScript, phương thức tĩnh (static methods) có thể được kế thừa từ lớp cha khi sử dụng từ khóa extends. Lớp con sẽ có quyền truy cập vào các phương thức tĩnh của lớp cha giống như các phương thức thông thường.

Tuy nhiên, phương thức tĩnh của lớp con không tự động ghi đè phương thức tĩnh của lớp cha. Nếu cần ghi đè, ta phải định nghĩa lại phương thức trong lớp con và sử dụng super.methodName() để gọi phương thức của lớp cha.

Cách sử dụng super.methodName() để gọi phương thức tĩnh của lớp cha

Từ khóa super trong phương thức tĩnh cho phép gọi phương thức tĩnh của lớp cha.

Ví dụ 1: Kế thừa phương thức tĩnh từ lớp cha

class Parent {
    static sayHello() {
        return "Xin chào từ lớp cha!";
    }
}

class Child extends Parent {}

console.log(Child.sayHello()); //  "Xin chào từ lớp cha!"

Lớp Child kế thừa phương thức sayHello() từ Parent mà không cần định nghĩa lại.

Ví dụ 2: Ghi đè phương thức tĩnh trong lớp con và gọi phương thức lớp cha

class Parent {
    static sayHello() {
        return "Xin chào từ lớp cha!";
    }
}

class Child extends Parent {
    static sayHello() {
        return super.sayHello() + " Và xin chào từ lớp con!";
    }
}

console.log(Child.sayHello()); 
//  "Xin chào từ lớp cha! Và xin chào từ lớp con!"

Ở đây, lớp Child ghi đè phương thức sayHello() nhưng vẫn sử dụng super.sayHello() để gọi phương thức từ Parent.

Ví dụ 3: Phương thức tĩnh sử dụng thuộc tính tĩnh của lớp cha

class Parent {
    static baseValue = 10;

    static getValue() {
        return this.baseValue;
    }
}

class Child extends Parent {}

console.log(Child.getValue()); //  10

Lớp Child kế thừa cả phương thức tĩnh getValue() và thuộc tính tĩnh baseValue từ Parent.

Khi nào nên sử dụng phương thức tĩnh?

Phương thức tĩnh có nhiều ứng dụng hữu ích, nhưng cần được sử dụng đúng cách để tránh ảnh hưởng đến khả năng mở rộng của mã nguồn.

Khi cần một phương thức hoạt động độc lập với instance của class

Phương thức tĩnh rất hữu ích khi thực hiện các thao tác chung mà không cần truy cập vào dữ liệu cụ thể của một instance.

Ví dụ: Tạo một class chứa hàm tiện ích
class MathUtils {
    static add(a, b) {
        return a + b;
    }

    static subtract(a, b) {
        return a - b;
    }
}

console.log(MathUtils.add(10, 5)); //  15
console.log(MathUtils.subtract(10, 5)); //  5

Ở đây, MathUtils chỉ chứa các phương thức tính toán và không cần tạo instance để sử dụng.

Khi muốn nhóm các logic chung vào một class mà không cần tạo đối tượng

Trong một số trường hợp, ta cần nhóm các phương thức xử lý dữ liệu chung vào một class thay vì để chúng rải rác trong mã nguồn.

Ví dụ: Quản lý cấu hình ứng dụng
class Config {
    static apiUrl = "https://api.example.com";
    static timeout = 5000;

    static getApiUrl() {
        return this.apiUrl;
    }
}

console.log(Config.getApiUrl()); //  "https://api.example.com"

Ở đây, Config chứa thông tin cấu hình của ứng dụng và ta có thể truy cập trực tiếp mà không cần tạo instance.

Khi cần một Factory Method để tạo đối tượng

Phương thức tĩnh có thể giúp tạo các instance với cấu hình mặc định mà không cần truyền nhiều tham số.

Ví dụ: Tạo tài khoản khách bằng Factory Method
class User {
    constructor(name, role) {
        this.name = name;
        this.role = role;
    }

    static createGuest() {
        return new User("Guest", "visitor");
    }
}

const guest = User.createGuest();
console.log(guest); //  User { name: 'Guest', role: 'visitor' }

Ở đây, createGuest() giúp tạo nhanh một user mặc định mà không cần nhập thông tin thủ công.

Những trường hợp không nên sử dụng phương thức tĩnh trong JavaScript

Khi phương thức cần truy cập dữ liệu từ instance

Nếu một phương thức cần truy cập hoặc thay đổi thuộc tính của một instance, không nên khai báo nó là static.

Ví dụ sai
class Car {
    constructor(brand) {
        this.brand = brand;
    }

    static showBrand() {
        return `Xe của bạn là ${this.brand}`; //  Lỗi: this.brand is undefined
    }
}

const myCar = new Car("Toyota");
console.log(myCar.showBrand()); //  Lỗi

Lỗi xảy ra vì phương thức showBrand() không thể truy cập this.brand do nó là phương thức tĩnh.

Khi cần sử dụng kế thừa mà không muốn phương thức bị ghi đè

Nếu một class được thiết kế để mở rộng, nhưng phương thức tĩnh không thể bị ghi đè hoặc mở rộng linh hoạt, nó có thể gây hạn chế.

Kết bài

Phương thức tĩnh (static methods) trong JavaScript là một công cụ mạnh mẽ giúp tổ chức mã nguồn tốt hơn, đặc biệt trong các tình huống yêu cầu các hàm tiện ích (utility functions), quản lý dữ liệu chung hoặc tạo phương thức Factory để khởi tạo đối tượng. Chúng hoạt động độc lập với instance của class, giúp mã nguồn trở nên rõ ràng và dễ bảo trì hơn.

Tuy nhiên, cần sử dụng phương thức tĩnh một cách hợp lý. Nếu một phương thức cần truy cập hoặc thay đổi trạng thái của instance, không nên khai báo nó là static. Ngoài ra, khi xây dựng các class có khả năng mở rộng, hãy cân nhắc xem liệu phương thức tĩnh có gây ra hạn chế trong kế thừa hay không.

Tóm lại, hiểu và áp dụng đúng phương thức tĩnh sẽ giúp cải thiện hiệu suất, khả năng tái sử dụng và tổ chức mã nguồn hiệu quả hơn trong lập trình JavaScript.

Bài viết liên quan