Tôi không có tài năng gì cả. Tôi chỉ đam mê hiểu biết
Tìm Hiểu Class Trong Javascript

Tìm Hiểu Class Trong Javascript


Ngày 27 Tháng 5 Năm 2020

Cũng như các ngôn ngữ khác thì Javascript cần phải được cải tiến để giúp người lập trình có thể dễ dàng hiểu và làm việc với nó được tốt hơn. Do đó trong phiên bản ES6 có cung cấp cho chúng ta một tiện ích tên là Class giúp hỗ trợ việc lập trình hướng đối tượng trong Javascript được tốt hơn. Hôm nay bạn hãy cùng mình đi vào tìm hiểu về khái niệm cũng như cách sử dụng class trong ngôn ngữ lập trình Javascript để phát triển web nhé.

Như chúng ta đã học ở bài trước thì việc kế thừa trong javascript có thể thực hiện thông qua Prototypal inheritance(Kế thừa nguyên mẫu). Nhưng đối với các bạn khi học đã học ngôn ngữ khác như Python, Java... thì sẽ hơi khó để có thiểu hiểu cách hoạt động của phương thức này. Do đó class được cung cấp để giải quyết vấn đề trên.
Và một vấn đề quan trọng là class trong javascript sẽ không giống như class ở các ngôn ngữ khác vì cốt lõi của nó được xây dựng dựa trên prototypeconstructor function nhưng cách hoạt động sẽ gần giống so vói các ngôn ngữ còn lại như kế thừa các phương thức, thuộc tính...

Nếu bạn muốn tìm hiểu thêm về lập trình hướng đối tượng thì có thể xem ở đây nhé. Bây giờ chúng ta sẽ cùng nhau đi vào tìm hiểu về nó nhé.

Cú Pháp Class Trong Javascript

Bây giờ chúng ta sẽ đi vào tìm hiểu cú pháp của nó nhé:

 class (Tên Class){
  constructor() {}
  Phương thức 1() {}
  Phương thức 2() {}
  ...
}

Chúng ta có thể sử dụng let đối tượng = new (Tên Class) để tạo một object(đối tượng) với các phương thức được tích hợp sẵn ở trong class đó. Và khi một đối tượng được tạo mới từ class bằng từ khóa new thì phương thức constructor() sẽ tự động được gọi, với tham số được truyền vào trong constructor là các thuộc tính thiết lập cho object đó. Bây giờ chúng ta sẽ cùng nhau đi vào ví dụ để dễ hình dung nhé.

Ví Dụ Class Trong Javascript

Bây giờ chúng ta sẽ tạo một class tên là KhachHang dùng để chứa phương thức hiển thị tên khách hàng ra ngoài màn hình và ten là tham số mà chúng ta truyền vào cho constructor. Để hiểu rõ hơn bạn xem đoạn code sau nhé:

Đoạn Code:

 class KhachHang { 
  constructor(ten) {
    this.ten = ten;
  }
  xinChao() {
    console.log("Tên Khách Hàng là " + this.ten);
  }
}

/*Tạo object mới từ Class*/
let an = new KhachHang("An");
an.xinChao();

Kết Quả:

Tạo object từ Class trong javascript

Khi mà new khachHang("An") được gọi thì một object mới sẽ được tạo từ class KhachHang và lưu vào trong biến an, sau đó phương thức constructor sẽ tự động chạy với tham số truyền vào là tên khách hàng dùng để gán giá trị cho thuộc tính ten của đối tượng đó.

Mình cũng có một số lưu ý khi bạn sử dụng class là:

  • Class chỉ chứa định nghĩa phương thức chứ không chứa thuộc tính của đối tượng.
  • Khi bạn định nghĩa các phương thức trong class thì không sử dấu phẩy(,) để ngăn cách chúng như trong object đâu nhé.
  • Bạn có thể tham chiếu đến thuộc tính của một object được xây dựng bằng class thông qua cách gọi trực tiếp. (Như ví dụ trên để lấy giá trị tên thì ta có thể gọi an.ten ).

Ví Dụ Constructor Function Trong Javascript

Để bạn hiểu được cốt lõi của tiện ích class thì mình sẽ thực hiện kế thừa phương thức như ví dụ trên nhưng thay vì sử dụng class thì sẽ dùng constructor function trong Javascript. Để hiểu rõ hơn bạn xem đoạn code sau nhé:

Đoạn Code:

 function KhachHang(ten) {
  this.ten = ten;
}
KhachHang.prototype.xinChao = function() {
  console.log("Tên Khách Hàng là " + this.ten);
};
let an = new KhachHang("An");
an.xinChao();

Kết Quả:

Sử dụng prototype thay thế class

Cách thức hoạt động của constructor function là:

  • Nó sẽ tạo một hàm KhachHang như là một constructor function với thuộc tính là ten.
  • Phương thức xinChao() sẽ được gán vào trong prototype của nó (KhachHang.prototype). Do đó nó có thể sử dụng phương thức cho các đối tượng được tạo bằng new KhachHang().

Để bạn thấy được sự tương đồng thì chúng ta sẽ kiểm tra loại của class bằng lệnh typeof() và các phương thức trong prototype của nó bằng lệnh getOwnPropertyNames() để xem kết quả sao nhé.

Đoạn Code:

 class KhachHang { 
  constructor(ten) {
    this.ten = ten;
  }
  xinChao() {
    console.log("Tên Khách Hàng là " + this.ten);
  }
}

console.log("Kiểu của class là " + (typeof KhachHang));
console.log("Phương thức trong class là " + Object.getOwnPropertyNames(KhachHang.prototype));

Kết Quả:

Kiểm tra kiểu dữ liệu class

Ở hai ví dụ trên bạn có thể thấy mối quan hệ giữa phương thức xinChao và class KhachHang cũng giống như mối quan hệ giữa phương thức xinChaoKhachHang.prototype.

Do đó chúng ta có thể xem classsyntactic sugar (cú pháp ngọt ngào) của constructor function.
Mình có lưu ý một tý, syntactic sugar là cú pháp được thiết kế để làm mọi thứ trở nên dễ hiểu hơn dựa trên những cái có sẵn trong ngôn ngữ lập trình.

Một Số Điểm Khác Biệt Giữa Class Và Constructor Function

Tuy hai cách trên có khá nhiều điểm tương đồng về cách hoạt động nhưng nó cũng có một số điểm khác biệt mà bạn cần chú ý là:

  • Khi chúng ta tạo mới một đối tượng từ class thì bắt buộc phải sử dụng từ khóa new nếu không thì chương trình sẽ xảy ra lỗi. Lỗi không sử dụng từ khóa new trong class
  • Tất cả đoạn mã trong class sẽ tự động thực hiện trong strict mode. Bạn có thể tham khảo thêm về strict modeđây nhé.
  • Các phương thức trong class là non-enumerable. Nghĩa là các phương thức đó sẽ không hiển thị trong vòng lặp. Bạn có thể tham khảo thêm về tính chất này ở đây nhé.
  • Không giống như hàm bình thường là chúng ta có thể gọi hàm trước rồi mới định nghĩa sau. Thì trong class không cho phép chúng ta làm điều đó. Ví dụ như chúng ta gọi đoạn mã tạo đối tượng let an = new KhachHang("An"); trước khi khai báo class KhachHang thì sẽ xảy ra lỗi. Lỗi khai báo trong class

Class Expression (Biểu Thức Class)

Giống như hàm thì class có thể được định nghĩa bên trong một biểu thức khác, hay sử dụng trong một khai báo biến để chuyển nó vào hàm như là một tham số... Để hiểu rõ hơn bạn xem đoạn code sau nhé:

Đoạn Code:

 let KhachHang = class { 
  xinChao() {
    console.log("Xin chào các bạn");
  }
}

/*Tạo object mới từ biến KhachHang*/
let an = new KhachHang();
an.xinChao();

Kết Quả:

Khai báo class trong biến

Chúng ta cũng có thể khai báo một class và trả nó về từ một hàm trong javascript . Để hiểu rõ hơn bạn xem đoạn code sau nhé:

Đoạn Code:

 function TaoClass (ten) { 
   return class {
    xinChao() {
      console.log("Xin chào " + ten);
    };
  };
}

/*Tạo một class mới*/
let KhachHang = TaoClass("Các Bạn");
let khachHang1 = new KhachHang();
khachHang1.xinChao();

Kết Quả:

Trả về Class từ hàm

Với cách tạo trên thì bạn có thể tùy chỉnh giá trị truyền vào cho phương thức xinChao() ở trong class.

Getter Và Setter

Cũng giống như object thì nó cũng có tính chất thuộc tính là getset dùng để đọc và gán giá trị cho thuộc tính. Bạn có thể tham khảo về hai tính chất này ở đây nhé. Bây giờ để dễ hình dung thì chúng ta sẽ đi vào ví dụ sau đây:

Đoạn Code:

 class KhachHang {
    constructor(ho, ten) {
        this.ho = ho;
        this.ten = ten;
    }

    /*Lấy giá trị bằng get*/
    get hoTen() {
        return this.ho + ' ' + this.ten;
    }
    /*Thiết lập giá trị mới bằng set*/
    set hoTen(chuoi) {
        let hoVaTen = chuoi.split(' ');
        if (hoVaTen.length === 2) {
            this.ho = hoVaTen[0];
            this.ten = hoVaTen[1];
        } else {
            throw 'Tên Không Hợp Lệ';
        }
    }
}

let an = new KhachHang('Nguyễn', 'An');
console.log("Lấy giá trị Họ Tên là " + an.hoTen);

/*Thiết lập tên mới cho đối tượng*/
an.fullName = 'Đỗ Lan';
console.log("Thiết lập giá trị mới Họ Tên là " + an.fullName);

Kết Quả:

Thuộc tính gat va set trong class

Chúng ta sẽ khởi tạo một class KhachHang có phương thức hoTen như là một getter và setter.
Với phương thức getter sẽ trả về giá trị của khách hàng bằng việc nối hai tham số là hoten.
Phương thức setter sẽ xử lý chuỗi được truyền vào và gán hai giá trị được tách từ chuỗi vào cho thuộc tính hoten. Sau đó trả về kết quả họ và tên của khách hàng.

Thiết Lập Tên Phương Thức Bằng Dấu [...]

Bạn có thiết lập tên cho phương thức bằng cách nối các chuỗi lại với nhau thông qua dấu []. Để hiểu rõ hơn bạn xem đoạn code sau nhé:

Đoạn Code:

 class KhachHang { 
  ['xin' + 'Chao']() {
    console.log("Xin Chào các bạn");
  }
}

/*Tạo object mới từ class*/
let khachHang = new KhachHang();
khachHang.xinChao();

Kết Quả:

Thiết lập tên phương thức trong class

Kế Thừa Trong Class Bằng extends

Trước khi ES6 được phát hành thì một trong những cách được sử dụng nhiều nhất để kế thừa từ các đối tượng khác là prototypal inheritance (kế thừa nguyên mẫu). Bạn có thể tham khảo phương thức này ở đây nhé.
Trong phần này thì chúng ta sẽ dùng cách khác đó là sử dụng từ khóa extend để kế thừa phương thức, constructor từ các class khác. Đầu tiên chúng ta sẽ đi vào tạo một class tên là DongVat với các phương thức an, ngu:

Class Động Vật:
 class DongVat { 
  constructor(ten) {
    this.ten = ten;
  }
  an() {
    console.log(`${this.ten} đang ăn.`);
  }
  ngu() {
    console.log(`${this.ten} đang ngủ.`);
  }
}

Bây giờ chúng ta sẽ tạo một class với tên là meo và nó được kế thừa các phương thức từ class DongVat bằng cách sử dụng từ khóa extends.

Class Mèo:
 class Meo extends DongVat {
  nhay() {
    console.log(`${this.ten} đang nhảy.`);
  }
}
let meoDen = new Meo("Mèo Đen");
meoDen.ngu();
meoDen.nhay();

Extends (mở rộng) trong trường hợp này bạn có thể hiểu là lớp con sẽ được mở rộng từ lớp cha bằng cách tổng hợp các phương thức từ lớp con và lớp cha.
Và khi bạn muốn kế thừa một phương thức thì có thể thực hiện theo cú pháp sau: class (Lớp Con) extends (Lớp Cha)

Bây giờ chúng ta sẽ đi kết hợp hai đoạn code trên để xem kết quả sao nhé:

Đoạn Code:

 class DongVat { 
  constructor(ten) {
    this.ten = ten;
  }
  an() {
    console.log(`${this.ten} đang ăn.`);
  }
  ngu() {
    console.log(`${this.ten} đang ngủ.`);
  }
}

class Meo extends DongVat {
  nhay() {
    console.log(`${this.ten} đang nhảy.`);
  }
}
let meoDen = new Meo("Mèo Đen");
meoDen.ngu(); /*Mèo Đen đang ngủ.*/
meoDen.nhay(); /*Mèo Đen đang nhảy.*/

Kết Quả:

Từ khóa extends trong class

Bạn có thể hình dung cách nó chạy như sau:

  • Đầu tiên ta sẽ tạo một object là meoDen dựa trên class Meo.
  • Khi chúng ta gọi các phương thức ngunhay của đối tượng, thì trước tiên nó sẽ tìm trong prototype của đối tượng đó là MeoDen.prototype (Chỉ có phương thức nhay).
  • Sau đó nó truy vấn ngược lên prototype của lớp cha là DongVat.prototype, tìm và trả về phương thức ngu còn thiếu.

Sử Dụng Từ Khóa super Trong Class

Nếu bạn cần điều chỉnh hay mở rộng hàm của lớp cha nhưng không muốn thay thế nó hoàn toàn thì ta có thể sử dụng super để giải quyết vấn đề trên. Và nó cũng có thể gọi constructor của lớp cha để sử dụng trong lớp con. Bây giờ chúng ta sẽ xem cú pháp của nó sau nhé:

  • super.(Phương thức)(...) dùng để gọi phương thức của lớp cha.
  • super(...) dùng để gọi constructor của lớp cha.

Gọi phương thức trong lớp cha:

Bây giờ chúng ta sẽ đi vào cách để gọi một phương thức cha trong phương thức con bằng super thông qua ví dụ dưới đây nhé:

Đoạn Code:

 class DongVat { 
  constructor(ten) {
    this.ten = ten;
  }
  an() {
    console.log(`${this.ten} đang ăn.`);
  }
  ngu() {
    console.log(`${this.ten} đang ngủ.`);
  }
}

class Meo extends DongVat {
  uong() {
    console.log(`${this.ten} đang uống.`);
  }
   an() {
    /*Gọi phương thức từ lớp cha*/
    super.an();
    /*Gọi phương thức trong lớp con*/
    this.uong();
  }
}
let meoDen = new Meo("Mèo Đen");
meoDen.an();

Kết Quả:

Gọi Phương thức trong lớp cha bằng super

Gọi constructor từ lớp cha:

Khi bạn thiết lập lớp con Meo với từ khóa extends thì constructor của nó sẽ được thừa hưởng từ lớp cha DongVat . Do đó nó không sở hữu constructor riêng cho mình và sẽ được tự động tạo ra như sau:

 class Meo extends DongVat {
  constructor(...args) {
     super(...args);
  }
}

Bây giờ chúng ta sẽ đi vào tìm hiểu cách tự tạo một constructor riêng cho class Meo bằng cách định nghĩa lại constructor của lớp cha (Animal) thông qua từ khóa super nhé:

Đoạn Code:

 class DongVat { 
  constructor(ten) {
    this.ten = ten;
  }
  an() {
    console.log(`${this.ten} đang ăn.`);
  }
  ngu() {
    console.log(`${this.ten} đang ngủ.`);
  }
}

class Meo extends DongVat {
  constructor(ten, mauSac){
    /*Thừa hưởng thuộc tính từ lớp cha*/
    super(ten);
    /*Xác định thuộc tính con*/
    this.mauSac = mauSac;
  }
  uong() {
    console.log(`${this.ten} ${this.mauSac} đang uống nước.`);
  }
}
let meoDen = new Meo("Mèo", "Đen");
meoDen.uong();

Kết Quả:

Thừa kế constructor từ class

Khi bạn sử dụng extends thì class DongVat được gọi là baseclass (Lớp cha) và class Meo được gọi là derived class (Lớp con). Javascript sẽ yêu cầu derived class (lớp con) muốn sở hữu constructor riêng thì phải gọi super() trước khi sử dụng this.
Để dễ hình dung thì cũng như ví dụ trên mình sẽ bỏ đi super để xem kết quả sao nhé:

Đoạn Code:

 class DongVat { 
  constructor(ten) {
    this.ten = ten;
  }
  an() {
    console.log(`${this.ten} đang ăn.`);
  }
  ngu() {
    console.log(`${this.ten} đang ngủ.`);
  }
}

class Meo extends DongVat {
  constructor(ten, mauSac){
    /*Sẽ gây ra lỗi khi ta không sử dụng từ khóa super*/
    this.ten = ten;
    /*Xác định thuộc tính con*/
    this.mauSac = mauSac;
  }
  uong() {
    console.log(`${this.ten} ${this.mauSac} đang uống nước.`);
  }
}
let meoDen = new Meo("Mèo", "Đen");
meoDen.uong();

Kết Quả:

Lỗi với từ khóa super trong class

Static Method (Phương thức tĩnh)

Static method sẽ được gọi mà không cần phải tạo mới đối tượng từ class đó và nó sẽ không sử dụng được khi lớp đó đã khởi tạo. Hay nói một cách dễ hiểu hơn là static method sẽ không có quyền truy cập vào dữ liệu được lưu trữ trong một đối tượng xác định.
Để dễ hình dung chúng ta sẽ đi vào ví dụ cụ thể sau đây nhé:

Đoạn Code:

 class KhachHang { 
  constructor(ten) {
    this.ten = ten;
  }
  static xinChao() {
    console.log("Xin Chào Các Bạn!");
  }
}

/*Gọi trực tiếp không cần khởi tạo đối tượng*/
KhachHang.xinChao();
/*Không sử dụng được khi lớp đó đã khởi tạo*/
let an = new KhachHang("An");
an.xinChao();

Kết Quả:

Sử dụng method static trong class

Tổng kết:

Qua đây mình mong bài viết sẽ giúp bạn hiểu được khái niệm cũng như cách sử dụng class trong ngôn ngữ Javascript và nếu có thắc mắc gì cứ gửi email mình sẽ phản hồi sớm nhất có thể. Rất mong bạn tiếp tục ủng hộ trang web để mình có thể viết nhiều bài hay hơn nữa nhé. Chúc bạn có một ngày vui vẻ!