NỘI DUNG BÀI HỌC

✅ CLASS TRONG JAVASCRIPT
✅ CÁC TRỤ CỘT CỦA OOP (Kế thừa, Đóng gói, Đa hình...)

🏗️ Phần 1: Vì sao cần OOP và “Class”, “Object” là gì?

 

Khi dự án còn nhỏ, bạn có thể viết code theo kiểu các biến và hàm rời rạc mà vẫn thấy ổn. Nhưng khi hệ thống lớn dần, nhiều màn hình, nhiều loại user, nhiều thao tác lặp lại xuất hiện, code rất dễ rơi vào trạng thái “mỳ ý”: chồng chéo, khó đọc, khó mở rộng và khó bảo trì.

Lập trình hướng đối tượng (OOP) ra đời để giúp bạn tổ chức code tốt hơn. Thay vì chỉ nghĩ theo các dòng lệnh rời, bạn bắt đầu gom dữ liệu và hành vi liên quan vào cùng một cấu trúc có ý nghĩa hơn.

 

🔹 1. Object là gì?

 

Object là một đối tượng cụ thể, có dữ liệu và hành động riêng. Ví dụ, một user có thể có username, role, isLoggedIn, và có thể thực hiện các hành động như login(), logout().

 

🔹 2. Class là gì?

 

Class là bản thiết kế để tạo ra nhiều object có cùng cấu trúc và hành vi. Nếu object là “ngôi nhà thật”, thì class là “bản vẽ” để xây ra nhiều ngôi nhà tương tự.

Điểm quan trọng nhất là: class giúp bạn định nghĩa cấu trúc một lần rồi dùng lại nhiều lần, thay vì copy-paste cùng một mẫu object ở nhiều nơi.

 

💻 Ví dụ hình dung nhanh:

 

// Đây là một object cụ thể, đã có dữ liệu thật
const userA = {
  username: "an.tester",
  role: "Admin",
  isLoggedIn: false
};

// Còn class sẽ là bản thiết kế để tạo ra nhiều user tương tự
class TestUser {
  constructor(username, role) {
    this.username = username;
    this.role = role;
    this.isLoggedIn = false;
  }
}

 

💡 Mẹo nhớ nhanh: class là bản thiết kế, còn object là sản phẩm thật được tạo ra từ bản thiết kế đó.

 

🧱 Phần 2: Cú pháp class trong JavaScript

 

JavaScript dùng từ khóa class để tạo ra một lớp. Bên trong class, bạn thường gặp hai phần quan trọng nhất là constructor() và các method.

 

🔹 1. constructor() là gì?

 

constructor() là hàm khởi tạo, được gọi tự động mỗi khi bạn tạo object mới bằng từ khóa new. Nhiệm vụ chính của nó là gán các giá trị ban đầu cho object mới.

Bên trong constructor(), từ khóa this tham chiếu tới chính object đang được tạo.

 

🔹 2. Method là gì?

 

Method là các hàm được định nghĩa bên trong class. Chúng đại diện cho các hành động mà object có thể thực hiện.

 

💻 Cú pháp class hoàn chỉnh:

 

class TestUser {
  constructor(username, role) {
    // this trỏ tới object mới đang được tạo
    this.username = username; // Gán username ban đầu
    this.role = role; // Gán role ban đầu
    this.isLoggedIn = false; // Giá trị mặc định
  }

  login() {
    this.isLoggedIn = true; // Cập nhật trạng thái đăng nhập
    console.log(`User '${this.username}' đã đăng nhập.`);
  }

  logout() {
    this.isLoggedIn = false; // Đặt lại trạng thái đăng xuất
    console.log(`User '${this.username}' đã đăng xuất.`);
  }
}

 

💻 Tạo object từ class bằng new:

 

const adminUser = new TestUser("admin_01", "Admin"); // Tạo object thứ nhất
const viewerUser = new TestUser("viewer_02", "Viewer"); // Tạo object thứ hai

adminUser.login(); // Gọi method của object adminUser
viewerUser.login(); // Gọi method của object viewerUser

console.log(adminUser.isLoggedIn); // true
console.log(viewerUser.role); // "Viewer"

 

⚠️ Lưu ý: Nếu quên từ khóa new khi tạo object từ class, JavaScript sẽ báo lỗi. Với class, new là bắt buộc.

 

⚖️ Phần 3: Khi nào dùng class, khi nào dùng object đơn lẻ?

 

Về bản chất, cả object literal và class đều có thể chứa dữ liệu và method. Khác biệt lớn nhất nằm ở mục đích sử dụng và khả năng tái sử dụng.

 

🔹 1. Object literal: phù hợp cho đối tượng đơn lẻ

 

Nếu bạn chỉ cần tạo một object duy nhất, không có nhu cầu sinh ra nhiều bản sao có cùng cấu trúc, object literal là cách rất ổn.

 

const userA = {
  name: "An",
  greet: function() {
    console.log(`Xin chào, tôi là ${this.name}`);
  }
};

userA.greet(); // "Xin chào, tôi là An"

Nhược điểm là nếu muốn tạo userB, userC với cùng hành vi, bạn rất dễ phải copy-paste lại cấu trúc cũ.

 

🔹 2. Class: phù hợp khi cần tạo nhiều object cùng mẫu

 

Khi có nhiều object giống nhau về cấu trúc nhưng khác dữ liệu đầu vào, class là lựa chọn tốt hơn hẳn.

 

class User {
  constructor(name) {
    this.name = name; // Mỗi object sẽ có name riêng
  }

  greet() {
    console.log(`Xin chào, tôi là ${this.name}`); // Method được định nghĩa một lần
  }
}

const user1 = new User("An");
const user2 = new User("Bình");

user1.greet(); // "Xin chào, tôi là An"
user2.greet(); // "Xin chào, tôi là Bình"

 

  • Dùng object literal: khi chỉ cần một object đơn lẻ, rõ ràng, không cần tái sử dụng nhiều.
  • Dùng class: khi cần tạo nhiều object cùng mẫu, hoặc muốn tổ chức code theo cấu trúc rõ ràng và mở rộng lâu dài.

 

🏛️ Phần 4: Các trụ cột quan trọng của OOP

 

Sức mạnh thật sự của OOP nằm ở 4 trụ cột cốt lõi: kế thừa, đóng gói, trừu tượngđa hình. Đây là phần giúp OOP khác hẳn việc chỉ gom dữ liệu vào một object đơn giản.

 

  • Inheritance: kế thừa lại thuộc tính và method từ class cha.
  • Encapsulation: đóng gói dữ liệu và che giấu phần nội bộ không nên cho bên ngoài can thiệp trực tiếp.
  • Abstraction: chỉ đưa ra giao diện cần thiết, ẩn đi phần phức tạp bên trong.
  • Polymorphism: cùng một tên method nhưng cách thực thi khác nhau tùy object.

 

🔹 1. Inheritance với extendssuper()

 

Kế thừa cho phép class con dùng lại dữ liệu và method từ class cha. Trong JavaScript, bạn dùng từ khóa extends để khai báo kế thừa, và dùng super() trong constructor của class con để gọi constructor của class cha.

 

💻 Ví dụ thực chiến với BasePage trong automation:

 

class BasePage {
  constructor(page) {
    this.page = page; // Lưu page để dùng chung cho mọi trang
    this.logoutButton = page.locator("#logout-button"); // Locator dùng chung
    this.searchBar = page.locator("#search-bar"); // Locator dùng chung
  }

  async logout() {
    await this.logoutButton.click(); // Hành động dùng chung
    console.log("LOG: Đã thực hiện đăng xuất.");
  }
}

class HomePage extends BasePage {
  constructor(page) {
    super(page); // Gọi constructor của class cha trước
    this.heroBanner = page.locator(".hero-banner"); // Locator riêng của HomePage
  }

  async clickHeroBanner() {
    await this.heroBanner.click(); // Method riêng của HomePage
  }
}

// const homePage = new HomePage(page);
// await homePage.clickHeroBanner(); // Method riêng của lớp con
// await homePage.logout(); // Method kế thừa từ lớp cha

Đây chính là một mẫu rất quen thuộc trong Page Object Model: gom phần dùng chung vào BasePage, còn từng trang cụ thể sẽ kế thừa và mở rộng thêm.

 

🔹 2. Encapsulation và Abstraction với private field

 

Đóng gói là gom dữ liệu và hành vi liên quan vào cùng một class, đồng thời che giấu các chi tiết nội bộ không nên bị thao tác bừa từ bên ngoài. Trừu tượng là chỉ cho người dùng class thấy một giao diện đơn giản để dùng.

Trong JavaScript hiện đại, bạn có thể dùng dấu # trước tên thuộc tính hoặc method để biến nó thành private.

 

💻 Ví dụ API Client:

 

class ApiClient {
  #authToken; // Thuộc tính private, không truy cập trực tiếp từ bên ngoài được

  constructor(baseURL) {
    this.baseURL = baseURL; // Thuộc tính public
  }

  async #authenticate() {
    console.log("LOG: Đang lấy token xác thực...");
    this.#authToken = "SECRET_TOKEN_123"; // Giả lập lấy token
  }

  async getProducts() {
    await this.#authenticate(); // Bên ngoài không cần biết bước lấy token diễn ra thế nào
    console.log(`LOG: Gửi yêu cầu đến ${this.baseURL}/products với token ${this.#authToken}`);
    return []; // Trả dữ liệu ra ngoài qua một method public đơn giản
  }
}

// const apiClient = new ApiClient("https://api.example.com");
// await apiClient.getProducts(); // Người dùng chỉ cần gọi method public này
// apiClient.#authenticate(); // Lỗi vì method private không gọi từ bên ngoài được

 

⚠️ Lưu ý: Private field với dấu # là cú pháp hiện đại. Nếu làm ở môi trường JavaScript cũ, bạn cần kiểm tra khả năng hỗ trợ trước khi dùng.

 

🔹 3. Polymorphism với method cùng tên nhưng hành vi khác nhau

 

Đa hình nghĩa là bạn có thể gọi cùng một method trên nhiều object khác nhau, nhưng kết quả thực thi sẽ thay đổi tùy vào loại object thật bên dưới.

 

💻 Ví dụ xử lý nhiều loại element:

 

class BaseElement {
  constructor(name) {
    this.name = name;
  }

  verify() {
    console.log(`Thực hiện kiểm tra chung cho '${this.name}'...`);
  }
}

class ButtonElement extends BaseElement {
  verify() {
    console.log(`✅ (Button) Đang kiểm tra xem nút '${this.name}' có hiển thị không...`);
  }
}

class InputElement extends BaseElement {
  constructor(name, expectedValue) {
    super(name); // Gọi constructor của class cha
    this.expectedValue = expectedValue; // Thuộc tính riêng của InputElement
  }

  verify() {
    console.log(`✅ (Input) Đang kiểm tra xem ô '${this.name}' có giá trị '${this.expectedValue}' hay không...`);
  }
}

const loginButton = new ButtonElement("Login");
const usernameInput = new InputElement("Username", "tomsmith");

const elementsToVerify = [loginButton, usernameInput];
elementsToVerify.forEach((element) => element.verify()); // Cùng gọi verify() nhưng hành vi khác nhau

Đây là một dạng rất hay gặp trong automation: cùng là “xác thực element”, nhưng cách xác thực cho button, input, dropdown hoặc toast message có thể khác nhau.

 

🧪 Phần 5: Ví dụ thực tế hơn với kế thừa, đóng gói và đa hình

 

Sau khi hiểu khái niệm, điều quan trọng hơn là nhìn thấy cách OOP giúp code automation bớt lặp, rõ vai trò hơn và mở rộng dễ hơn.

 

🔹 1. Ví dụ về kế thừa với các loại user

 

class BaseUser {
  constructor(username) {
    this.username = username; // Thuộc tính chung cho mọi user
  }

  login() {
    console.log(`User '${this.username}' đã đăng nhập.`);
  }

  logout() {
    console.log(`User '${this.username}' đã đăng xuất.`);
  }
}

class AdminUser extends BaseUser {
  deleteUser(targetUsername) {
    console.log(`Admin '${this.username}' đang xóa người dùng '${targetUsername}'.`);
  }
}

const guest = new BaseUser("guest123");
const admin = new AdminUser("super_admin");

guest.login(); // Dùng method từ BaseUser
admin.login(); // Kế thừa method từ BaseUser
admin.deleteUser("guest123"); // Method riêng của AdminUser

 

🔹 2. Ví dụ về đóng gói với bộ đếm thời gian test

 

class TestTimer {
  #startTime; // Thuộc tính private
  #endTime; // Thuộc tính private

  constructor() {
    this.#startTime = 0;
    this.#endTime = 0;
  }

  start() {
    this.#startTime = Date.now(); // Chỉ class tự quản lý thời điểm bắt đầu
    console.log("LOG: Bắt đầu bấm giờ.");
  }

  stop() {
    this.#endTime = Date.now(); // Chỉ class tự quản lý thời điểm kết thúc
    console.log("LOG: Dừng bấm giờ.");
  }

  getDuration() {
    if (this.#startTime === 0 || this.#endTime === 0) {
      return "Lỗi: Cần start() và stop() trước khi lấy thời gian.";
    }

    const duration = (this.#endTime - this.#startTime) / 1000;
    return `Thời gian thực thi test: ${duration.toFixed(2)} giây.`;
  }
}

const loginTestTimer = new TestTimer();
loginTestTimer.start();
loginTestTimer.stop();
console.log(loginTestTimer.getDuration());

// loginTestTimer.#startTime = 123; // Lỗi vì private field không truy cập từ ngoài class được

 

🔹 3. Ghi nhớ nhanh cho Tester

 

  • Dùng object literal khi chỉ cần một object đơn lẻ, không cần sinh nhiều bản sao.
  • Dùng class khi cần tạo nhiều object có cùng cấu trúc và hành vi.
  • constructor() dùng để gán dữ liệu ban đầu cho object mới.
  • this trong class thường trỏ tới object đang được tạo hoặc đang gọi method đó.
  • extendssuper() là bộ đôi quan trọng để làm kế thừa.
  • Dấu # giúp tạo private field hoặc private method để che giấu phần nội bộ.
  • Đa hình rất hữu ích khi bạn có nhiều loại object khác nhau nhưng muốn gọi cùng một tên method.
  • Trong automation, OOP rất hợp với Page Object Model, API client, wrapper cho element và user model.

 

Kết luận: Bài 7 là nền móng để bạn chuyển từ viết các hàm rời rạc sang tổ chức code theo mô hình có cấu trúc hơn. Nếu hiểu chắc class, object, constructor, extends, super() và các trụ cột của OOP, bạn sẽ dễ dàng đi tiếp sang Page Object Model và các framework automation chuyên nghiệp hơn.

Teacher

Teacher

Nguyên Hoàng

Automation Engineer

With 7+ years of hands-on experience across multiple languages and frameworks. I'm here to share knowledge, helping you turn complex processes into simple and effective solutions.

Cộng đồng Automation Testing Việt Nam:

🌱 Telegram Automation Testing:   Cộng đồng Automation Testing
🌱 
Facebook Group Automation: Cộng đồng Automation Testing Việt Nam
🌱 
Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium
🌱 Telegram
Manual Testing:   Cộng đồng Manual Testing
🌱 
Facebook Group Manual: Cộng đồng Manual Testing Việt Nam

Chia sẻ khóa học lên trang

Bạn có thể đăng khóa học của chính bạn lên trang Anh Tester để kiếm tiền

Danh sách bài học