Skip to content

Lập trình Hướng Đối Tượng (OOP) trong JavaScript

Javascript9 min read

Nếu bạn đang học JavaScript và muốn viết mã nguồn gọn gàng, dễ bảo trì, thì Lập trình Hướng Đối Tượng (OOP) là kỹ năng không thể bỏ qua. Đừng lo nếu bạn mới bắt đầu – bài viết này sẽ dẫn bạn qua các khái niệm OOP một cách dễ hiểu, với những ví dụ gần gũi như quán phở hay xe máy ngoài đường phố Việt Nam. Hãy cùng khám phá và bắt tay vào code ngay nhé!

OOP là gì? Tại sao bạn cần nó?

Hãy tưởng tượng bạn đang mở một quán phở. Mỗi tô phở (phở bò, phở gà) là một object (đối tượng), còn công thức làm phở (nguyên liệu, cách nấu) là một class (lớp) – bản thiết kế để tạo ra các tô phở. OOP giúp bạn tổ chức mã nguồn giống như cách bạn quản lý quán phở: mọi thứ được sắp xếp khoa học, dễ tái sử dụng, và dễ mở rộng (thêm món phở mới chẳng hạn!).

Trong JavaScript, OOP dựa trên cơ chế prototype, khác với các ngôn ngữ như Java hay C++. Điều này làm JavaScript linh hoạt nhưng cũng hơi "lắt léo" với người mới. Đừng lo, chúng ta sẽ đi từng bước để làm sáng tỏ!

OOP mang lại các lợi ích sau:

  • Tái sử dụng mã: Viết một lần, dùng nhiều nơi.
  • Dễ bảo trì: Mã được tổ chức gọn gàng, dễ sửa đổi.
  • Mô phỏng thế giới thực: Dễ dàng biểu diễn các khái niệm như học sinh, xe cộ, hay đơn hàng.

Class và Object: Nền tảng của OOP

Class: Bản thiết kế

Một class giống như công thức nấu phở – nó không phải là tô phở thật, mà là hướng dẫn để tạo ra tô phở. Trong JavaScript, bạn định nghĩa class bằng từ khóa class, với các properties (thuộc tính) và methods (phương thức).

Ví dụ: Hãy tạo một class Pho cho quán phở của bạn:

class Pho {
constructor(loai, gia, topping) {
this.loai = loai; // Phở bò, phở gà, v.v.
this.gia = gia; // Giá tiền
this.topping = topping; // Quẩy, hành, ớt, v.v.
}
moTa() {
console.log(`${this.loai} giá ${this.gia} VNĐ, có ${this.topping}.`);
}
}

Object: Sản phẩm từ bản thiết kế

Một object là một instance (thể hiện) của class. Từ class Pho, bạn có thể tạo ra nhiều tô phở khác nhau:

const phoBo = new Pho('Phở bò tái', 50000, 'quẩy, hành lá');
const phoGa = new Pho('Phở gà', 45000, 'hành phi, chanh');
phoBo.moTa(); // Tô Phở bò tái giá 50000 VNĐ, có quẩy, hành lá.
phoGa.moTa(); // Tô Phở gà giá 45000 VNĐ, có hành phi, chanh.

Hãy thử ngay: Mở trình duyệt, nhấn F12, vào tab Console, sao chép code trên và thử tạo một tô phở theo ý bạn!

Bốn nguyên tắc OOP

OOP dựa trên 4 nguyên tắc cốt lõi, giúp bạn viết mã như một lập trình viên chuyên nghiệp. Hãy cùng khám phá qua các ví dụ thực tế:

1. Abstraction (Tính Trừu tượng)

Abstraction là việc ẩn đi các chi tiết phức tạp và chỉ hiển thị những gì cần thiết. Khi bạn gọi món phở qua app, bạn chỉ cần chọn "Phở bò tái, thêm quẩy", mà không cần biết quán nấu thế nào (đun nước dùng, thái thịt, v.v.).

Trong code, phương thức moTa() là một dạng abstraction – bạn không cần biết chi tiết cách hiển thị thông tin, chỉ cần gọi là xong.

2. Encapsulation (Tính Đóng gói)

Encapsulation giống như việc giữ công thức phở trong két sắt – chỉ quán được biết, khách không thể can thiệp. Trong JavaScript, bạn dùng ký hiệu # để tạo private properties hoặc private methods (riêng tư), đảm bảo chúng không bị truy cập từ bên ngoài.

Ví dụ:

class Pho {
#congThucBiMat = 'Nước dùng ninh từ xương bò 12 tiếng'; // Private
constructor(loai, gia, topping) {
this.loai = loai;
this.gia = gia;
this.topping = topping;
}
moTa() {
console.log(`${this.loai} giá ${this.gia} VNĐ, có ${this.topping}.`);
}
#tinhToanNguyenLieu() {
// Private method
console.log('Tính toán nguyên liệu bí mật...');
}
}
const phoBo = new Pho('Phở bò tái', 50000, 'quẩy');
console.log(phoBo.#congThucBiMat); // Lỗi: Không thể truy cập
phoBo.#tinhToanNguyenLieu(); // Lỗi: Không thể gọi

Bạn cũng có thể dùng gettersetter để kiểm soát việc truy cập thuộc tính:

class Pho {
#gia;
constructor(loai, gia, topping) {
this.loai = loai;
this.#gia = gia;
this.topping = topping;
}
get gia() {
return this.#gia;
}
set gia(giaMoi) {
if (giaMoi < 0) {
console.log('Giá không thể âm!');
} else {
this.#gia = giaMoi;
}
}
}
const phoBo = new Pho('Phở bò tái', 50000, 'quẩy');
console.log(phoBo.gia); // 50000
phoBo.gia = -1000; // Giá không thể âm!
phoBo.gia = 60000;
console.log(phoBo.gia); // 60000

3. Inheritance (Tính Kế thừa)

Inheritance cho phép một class (lớp con) kế thừa các properties và methods từ một class khác (lớp cha), giúp tiết kiệm công sức. Ví dụ, nếu quán phở thêm món bún bò, bạn không cần viết lại công thức từ đầu mà có thể tận dụng công thức phở.

class BunBo extends Pho {
constructor(loai, gia, topping, saTe) {
super(loai, gia, topping); // Gọi constructor của lớp cha
this.saTe = saTe; // Thêm thuộc tính mới
}
moTa() {
console.log(
`${this.loai} giá ${this.gia} VNĐ, có ${this.topping}${this.saTe}.`
);
}
}
const bunBoHue = new BunBo('Bún bò Huế', 55000, 'hành lá', 'sa tế cay');
bunBoHue.moTa(); // Tô Bún bò Huế giá 55000 VNĐ, có hành lá và sa tế cay.

4. Polymorphism (Tính Đa hình)

Polymorphism có nghĩa là "nhiều hình dạng". Nó cho phép các lớp con override (ghi đè) methods của lớp cha để thực hiện hành vi khác nhau. Trong ví dụ trên, phương thức moTa() của BunBo khác với Pho, dù cả hai cùng tên.

Một ví dụ khác:

class KhachHang {
datMon() {
console.log('Khách hàng đang đặt món...');
}
}
class KhachVip extends KhachHang {
datMon() {
console.log('Khách VIP đặt món với ưu tiên đặc biệt!');
}
}
const khachThuong = new KhachHang();
const khachVip = new KhachVip();
khachThuong.datMon(); // Khách hàng đang đặt món...
khachVip.datMon(); // Khách VIP đặt món với ưu tiên đặc biệt!

Prototype: Linh hồn của OOP trong JavaScript

Không như các ngôn ngữ OOP cổ điển, JavaScript sử dụng prototype-based inheritance thay vì class-based. Mỗi object trong JavaScript đều liên kết với một prototype, và nó có thể "mượn" các methods hoặc properties từ prototype đó. Đây là cơ chế cốt lõi của OOP trong JavaScript.

Ví dụ, khi bạn dùng array.push(), bạn đang gọi method từ Array.prototype:

const mang = [1, 2, 3];
console.log(mang.__proto__ === Array.prototype); // true

Triển khai OOP với Prototype

Trước khi có cú pháp class (ES6), JavaScript dùng constructor functions để tạo object và thiết lập prototype:

function XeMay(hang, mauSac) {
this.hang = hang;
this.mauSac = mauSac;
}
XeMay.prototype.chay = function () {
console.log(`Chiếc ${this.hang} màu ${this.mauSac} đang chạy vèo vèo!`);
};
const wave = new XeMay('Honda Wave', 'đỏ');
wave.chay(); // Chiếc Honda Wave màu đỏ đang chạy vèo vèo!

Cú pháp class trong ES6 chỉ là syntactic sugar (cách viết gọn) cho constructor functions, nhưng nó dễ đọc hơn:

class XeMay {
constructor(hang, mauSac) {
this.hang = hang;
this.mauSac = mauSac;
}
chay() {
console.log(`Chiếc ${this.hang} màu ${this.mauSac} đang chạy vèo vèo!`);
}
}
const wave = new XeMay('Honda Wave', 'đỏ');
wave.chay(); // Chiếc Honda Wave màu đỏ đang chạy vèo vèo!

Sử dụng Object.create()

Một cách khác để tạo object với prototype là dùng Object.create():

const Xe = {
chay() {
console.log(`Chiếc ${this.hang} màu ${this.mauSac} đang chạy!`);
},
};
const wave = Object.create(Xe);
wave.hang = 'Honda Wave';
wave.mauSac = 'đỏ';
wave.chay(); // Chiếc Honda Wave màu đỏ đang chạy!

Static Methods: Công cụ của lớp

Static methods là các phương thức thuộc về class, không phải instance. Chúng hữu ích khi bạn muốn thực hiện chức năng liên quan đến class mà không cần tạo object.

Ví dụ:

class Pho {
constructor(loai, gia) {
this.loai = loai;
this.gia = gia;
}
static tinhGiaTrungBinh(dsPho) {
const tongGia = dsPho.reduce((tong, pho) => tong + pho.gia, 0);
return tongGia / dsPho.length;
}
}
const phoBo = new Pho('Phở bò', 50000);
const phoGa = new Pho('Phở gà', 45000);
console.log(Pho.tinhGiaTrungBinh([phoBo, phoGa])); // 47500

Thực hành: Xây dựng hệ thống quản lý quán phở

Hãy thử áp dụng những gì bạn đã học vào một bài tập thực tế:

  1. Tạo một class QuanPho với các thuộc tính tenQuan, diaChi, và một mảng danhSachMon (chứa các object Pho).
  2. Thêm phương thức themMon(loai, gia, topping) để thêm món phở mới.
  3. Tạo một class QuanPhoCaoCap kế thừa từ QuanPho, với phương thức khuyenMai() để giảm giá 10% cho tất cả món.
  4. (Nâng cao) Thêm private property #doanhThu và phương thức tinhDoanhThu() để tính tổng giá các món đã bán.

Gợi ý code khởi đầu:

class QuanPho {
#doanhThu = 0;
constructor(tenQuan, diaChi) {
this.tenQuan = tenQuan;
this.diaChi = diaChi;
this.danhSachMon = [];
}
themMon(loai, gia, topping) {
const monMoi = new Pho(loai, gia, topping);
this.danhSachMon.push(monMoi);
this.#doanhThu += gia;
}
}

Hãy thử ngay: Sao chép code, hoàn thiện các phần còn lại, và chạy trên JSFiddle hoặc Console trình duyệt.

Tổng kết

OOP trong JavaScript giống như học nấu phở: ban đầu hơi lạ lẫm, nhưng khi nắm được công thức, bạn sẽ tạo ra những "tô phở" mã nguồn ngon lành! Với các khái niệm như class, object, prototype, và 4 nguyên tắc OOP, bạn đã có nền tảng để viết mã linh hoạt và dễ bảo trì.

Hãy tiếp tục thực hành:

  • Thử tạo các class mô phỏng các tình huống thực tế (như quản lý học sinh, xe cộ, hay đơn hàng).
  • Tìm hiểu sâu hơn về getter/setter, static methods, hoặc Object.create().
  • Đọc thêm tài liệu từ MDN Object-oriented programming (OOP) để nắm vững prototype.

Cảm ơn bạn đã đồng hành qua bài viết này! Hãy tiếp tục hành trình lập trình đầy thú vị và đừng ngại mắc lỗi – đó là cách bạn học nhanh nhất!

© 2025 by Coding With WanBi. All rights reserved.
Theme by LekoArts