Trước ES6, lập trình hướng đối tượng trong JS gây rất nhiều khó khăn cho những người đã quen thuộc với các ngôn ngữ như Java, C# hay Python. Với class, ES6 đã giới thiệu tiêu chuẩn lập trình hướng đối tượng mới giúp chúng ta tiếp cận một cách tự nhiên hơn mà vẫn giữ được những điểm mạnh của JS trước đó.
Khai báo lớp
Lớp (class) được khai báo với từ khóa class như ví dụ lớp Person sau:
class Person {
}
Hàm constructor
Với các ngôn ngữ như C# hay Java, hàm constructor có tên hàm trùng với tên lớp và không có kiểu trả về. Trong JS, hàm này dùng từ khóa constructor như sau:
class Person {
constructor(name) {
this.name = name;
}
}
Ở đây chúng ta cần phân biệt hai biến name, một biến name là biến cục bộ của hàm khởi tạo và một biến name là biến của lớp Person. Dùng từ khóa this để phân biệt hai biến trùng tên.
Lớp được sử dụng thông qua thể hiện (instance) như sau:
const minh = new Person('Ngoc Minh Tran');
Chúng ta có thể truy cập trực tiếp biến name của lớp Person thông qua thể hiện như sau:
alert(minh.name); // Ngoc Minh Tran
Trong các ngôn ngữ như C# hay Java có hỗ trợ chức năng quá tải constructor cho phép chúng ta định nghĩa nhiều hàm constructor trong một lớp, tuy nhiên, JS không hỗ trợ điều này. Trong JS, một lớp chỉ chứa một constructor.
Phương thức của lớp
Một hàm (function) được khai báo trong một lớp gọi là một phương thức (method). Khi khai báo phương thức chúng ta không dùng từ khóa function như ví dụ phương thức hello của lớp Person:
class Person {
constructor(name) {
this.name = name;
}
hello() {
return 'Hello, I am ' + this.name + '.';
}
}
Phương thức hello() phải được truy cập qua thể hiện của lớp Person:
alert(minh.hello());
Phương thức có thể được truy cập trực tiếp thông qua tên lớp nếu chúng ta khai báo phương thức này là phương thức tĩnh bằng cách dùng từ khóa static trước tên phương thức như sau:
static sHello() {
return 'Hello, I am a static method.';
}
Truy cập đến phương thức này:
alert(Person.sHello());
Trong JS chưa hỗ trợ các phương thức private hay protected.
Tính đóng gói – Getter / Setter
Như đã đề cập ở phần trên, chúng ta có thể truy cập trực tiếp đến biến name thông qua các thể hiện của lớp Person. Tuy nhiên, trong mô hình hướng đối tượng, cách truy cập hiệu quả nhất đến các biến của lớp là sử dụng các setter hay getter. Đây là nguyên tắc đóng gói – một trong những trụ cột quan trọng nhất trong lập trình hướng đối tượng. Định nghĩa lại lớp Person dùng setter (setName) và getter (getName) như sau và chú ý cách dùng:
class Person {
constructor(name) {
this.name = name;
}
set setName(value){
this.name = value;
}
get getName() {
return this.name;
}
hello() {
return 'Hello, I am ' + this.name + '.';
}
static sHello(){
return "Hi everyone, I am a static method.";
}
}
const minh = new Person();
minh.setName = 'Ngoc Minh';
alert(minh.getName);
Giống Java hay C#, setter bắt đầu bằng từ khóa set và getter bắt đầu bằng get. Một lớp có thể chỉ có setter hay getter hoặc cả hai.
Thừa kế
Một trong những ưu điểm của mô hình hướng đối tượng là khả năng kế thừa. Chúng ta có thể định nghĩa một lớp kế thừa từ một lớp khác dùng từ khóa extends như ví dụ sau:
class Programmer extends Person{
...
}
Lúc này, lớp Programmer sẽ thừa kế các thành phần từ lớp Person:
const minh = new Programmer('Minh');
alert(minh.hello());
Tính đa hình
Chúng ta có thể định nghĩa lại phương thức hello() trong lớp Programmer:
class Programmer extends Person{
hello() {
return super.hello() + " I am a programmer."
}
}
Ở đây chúng ta đã dùng từ khóa super để tham chiếu đến lớp cha Person. Việc định nghĩa lại phương thức hello() nghĩa là chúng đã ghi đè lên phương thức hello() mà lớp Programmer kế thừa từ Person.