Classes
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2016.
Classes là một khuôn mẫu để tạo đối tượng. Chúng đóng gói dữ liệu cùng với code xử lý dữ liệu đó. Classes trong JS được xây dựng trên nền tảng prototype nhưng cũng có một số cú pháp và ngữ nghĩa riêng biệt.
Để xem thêm ví dụ và giải thích, hãy xem hướng dẫn Sử dụng classes.
Mô tả
>Định nghĩa classes
Classes thực chất là "hàm đặc biệt", và giống như bạn có thể định nghĩa biểu thức hàm và khai báo hàm, một class cũng có thể được định nghĩa theo hai cách: biểu thức class hoặc khai báo class.
// Khai báo
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
// Biểu thức; class ẩn danh nhưng được gán cho một biến
const Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
// Biểu thức; class có tên riêng
const Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
Giống như biểu thức hàm, biểu thức class có thể ẩn danh hoặc có tên khác với biến mà nó được gán vào. Tuy nhiên, khác với khai báo hàm, khai báo class có cùng hạn chế vùng chết tạm thời như let hoặc const và hoạt động như thể chúng không được hoisting.
Thân class
Thân của một class là phần nằm trong dấu ngoặc nhọn {}. Đây là nơi bạn định nghĩa các thành phần của class, chẳng hạn như các phương thức hoặc constructor.
Thân của class được thực thi trong strict mode ngay cả khi không có chỉ thị "use strict".
Một phần tử class có thể được phân loại theo ba khía cạnh:
- Loại: Getter, setter, phương thức, hoặc trường (field)
- Vị trí: Static hoặc instance
- Khả năng hiển thị: Public hoặc private
Kết hợp lại, chúng tạo ra 16 tổ hợp có thể có. Để chia nhỏ tài liệu tham khảo một cách hợp lý hơn và tránh nội dung trùng lặp, các phần tử khác nhau được giới thiệu chi tiết trên các trang riêng biệt:
- Định nghĩa phương thức
-
Phương thức instance public
- getter
-
Getter instance public
- setter
-
Setter instance public
- Trường class public
-
Trường instance public
static-
Phương thức static public, getter, setter, và trường
- Các phần tử private
-
Tất cả những gì là private
Note: Các phần tử private có hạn chế là tất cả tên private được khai báo trong cùng một class phải là duy nhất. Tất cả các thuộc tính public khác không có hạn chế này — bạn có thể có nhiều thuộc tính public cùng tên, và thuộc tính cuối cùng sẽ ghi đè các thuộc tính trước. Đây là hành vi tương tự như trong các bộ khởi tạo đối tượng.
Ngoài ra, còn có hai cú pháp phần tử class đặc biệt: constructor và các khối khởi tạo static, mỗi cái đều có trang tham khảo riêng.
Constructor
Phương thức constructor là một phương thức đặc biệt dùng để tạo và khởi tạo một đối tượng được tạo bằng class. Chỉ được phép có một phương thức đặc biệt tên "constructor" trong một class — một SyntaxError sẽ được ném ra nếu class chứa nhiều hơn một constructor.
Một constructor có thể sử dụng từ khóa super để gọi constructor của lớp cha.
Bạn có thể tạo các thuộc tính instance bên trong constructor:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
Ngoài ra, nếu giá trị của các thuộc tính instance không phụ thuộc vào tham số của constructor, bạn có thể định nghĩa chúng dưới dạng trường class.
Các khối khởi tạo static
Các khối khởi tạo static cho phép khởi tạo linh hoạt các thuộc tính static, bao gồm cả việc đánh giá các câu lệnh trong quá trình khởi tạo, đồng thời cho phép truy cập vào phạm vi private.
Có thể khai báo nhiều khối static, và chúng có thể xen kẽ với khai báo các trường và phương thức static (tất cả các mục static được đánh giá theo thứ tự khai báo).
Các phương thức
Các phương thức được định nghĩa trên prototype của mỗi instance class và được chia sẻ bởi tất cả các instance. Các phương thức có thể là hàm thông thường, hàm async, hàm generator, hoặc hàm generator async. Để biết thêm thông tin, xem định nghĩa phương thức.
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Phương thức
calcArea() {
return this.height * this.width;
}
*getSides() {
yield this.height;
yield this.width;
yield this.height;
yield this.width;
}
}
const square = new Rectangle(10, 10);
console.log(square.area); // 100
console.log([...square.getSides()]); // [10, 10, 10, 10]
Các phương thức và trường static
Từ khóa static định nghĩa một phương thức hoặc trường static cho một class. Các thuộc tính static (trường và phương thức) được định nghĩa trên chính class thay vì trên từng instance. Các phương thức static thường được dùng để tạo các hàm tiện ích cho ứng dụng, trong khi các trường static hữu ích cho bộ nhớ đệm, cấu hình cố định, hoặc bất kỳ dữ liệu nào không cần được sao chép qua các instance.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Point";
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined
console.log(Point.displayName); // "Point"
console.log(Point.distance(p1, p2)); // 7.0710678118654755
Khai báo trường
Với cú pháp khai báo trường class, ví dụ constructor có thể được viết lại như sau:
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
Các trường class tương tự như thuộc tính đối tượng, không phải biến, vì vậy chúng ta không dùng các từ khóa như const để khai báo chúng. Trong JavaScript, các phần tử private sử dụng cú pháp định danh đặc biệt, vì vậy các từ khóa bổ nghĩa như public và private cũng không nên được dùng.
Như thấy ở trên, các trường có thể được khai báo có hoặc không có giá trị mặc định. Các trường không có giá trị mặc định sẽ mặc định là undefined. Bằng cách khai báo trước các trường, định nghĩa class trở nên tự mô tả hơn, và các trường luôn hiện diện, giúp tối ưu hóa.
Xem trường class public để biết thêm thông tin.
Các phần tử private
Sử dụng trường private, định nghĩa có thể được tinh chỉnh như dưới đây.
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
Việc tham chiếu đến các trường private từ bên ngoài class sẽ gây ra lỗi; chúng chỉ có thể được đọc hoặc ghi trong thân class. Bằng cách định nghĩa những thứ không hiển thị bên ngoài class, bạn đảm bảo rằng người dùng class của bạn không thể phụ thuộc vào các chi tiết nội bộ, vốn có thể thay đổi qua các phiên bản.
Các trường private chỉ có thể được khai báo trước trong một khai báo trường. Chúng không thể được tạo sau đó thông qua việc gán giá trị cho chúng, không giống như các thuộc tính thông thường.
Các phương thức private và accessor cũng có thể được định nghĩa bằng cú pháp tương tự như các đối tác public của chúng, nhưng với định danh bắt đầu bằng #.
Để biết thêm thông tin, xem các phần tử private.
Kế thừa
Từ khóa extends được dùng trong khai báo class hoặc biểu thức class để tạo một class là con của một constructor khác (có thể là class hoặc hàm).
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // gọi constructor của lớp cha và truyền tham số name
}
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.
Nếu lớp con có constructor, nó cần gọi super() trước khi sử dụng this. Từ khóa super cũng có thể được dùng để gọi các phương thức tương ứng của lớp cha.
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(`${this.name} roars.`);
}
}
const l = new Lion("Fuzzy");
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.
Thứ tự đánh giá
Khi một khai báo class hoặc biểu thức class được đánh giá, các thành phần của nó được đánh giá theo thứ tự sau:
- Mệnh đề
extends, nếu có, được đánh giá trước tiên. Nó phải trả về một hàm constructor hợp lệ hoặcnull, nếu không sẽ ném raTypeError. - Phương thức
constructorđược trích xuất, thay thế bằng triển khai mặc định nếuconstructorkhông có. Tuy nhiên, vì định nghĩaconstructorchỉ là một định nghĩa phương thức, bước này không thể quan sát được. - Các khóa thuộc tính của phần tử class được đánh giá theo thứ tự khai báo. Nếu khóa thuộc tính được tính toán, biểu thức tính toán đó được đánh giá, với giá trị
thisđược đặt thành giá trịthisbao quanh class (không phải chính class đó). Chưa có giá trị thuộc tính nào được đánh giá ở bước này. - Các phương thức và accessor được cài đặt theo thứ tự khai báo. Các phương thức và accessor của instance được cài đặt trên thuộc tính
prototypecủa class hiện tại, và các phương thức và accessor static được cài đặt trên chính class đó. Các phương thức và accessor instance private được lưu lại để cài đặt trực tiếp trên instance sau này. Bước này không thể quan sát được. - Class hiện được khởi tạo với prototype được chỉ định bởi
extendsvà triển khai được chỉ định bởiconstructor. Đối với tất cả các bước trên, nếu một biểu thức được đánh giá cố gắng truy cập tên của class, mộtReferenceErrorsẽ được ném ra vì class chưa được khởi tạo. - Các giá trị của phần tử class được đánh giá theo thứ tự khai báo:
- Đối với mỗi trường instance (public hoặc private), biểu thức khởi tạo của nó được lưu lại. Biểu thức khởi tạo được đánh giá trong quá trình tạo instance, ở đầu constructor (đối với base class) hoặc ngay trước khi lệnh gọi
super()trả về (đối với derived class). - Đối với mỗi trường static (public hoặc private), biểu thức khởi tạo của nó được đánh giá với
thisđược đặt thành chính class đó, và thuộc tính được tạo ra trên class. - Các khối khởi tạo static được đánh giá với
thisđược đặt thành chính class đó.
- Đối với mỗi trường instance (public hoặc private), biểu thức khởi tạo của nó được lưu lại. Biểu thức khởi tạo được đánh giá trong quá trình tạo instance, ở đầu constructor (đối với base class) hoặc ngay trước khi lệnh gọi
- Class hiện đã được khởi tạo hoàn toàn và có thể được sử dụng như một hàm constructor.
Để biết cách các instance được tạo, xem tài liệu tham khảo constructor.
Ví dụ
>Ràng buộc this với các phương thức instance và static
Khi một phương thức static hoặc instance được gọi mà không có giá trị cho this, chẳng hạn bằng cách gán phương thức đó cho một biến rồi gọi nó, giá trị this sẽ là undefined bên trong phương thức đó. Hành vi này vẫn xảy ra ngay cả khi không có chỉ thị "use strict", vì code trong thân class luôn được thực thi trong strict mode.
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
const obj = new Animal();
obj.speak(); // đối tượng Animal
const speak = obj.speak;
speak(); // undefined
Animal.eat(); // class Animal
const eat = Animal.eat;
eat(); // undefined
Nếu chúng ta viết lại ví dụ trên bằng cú pháp dựa trên hàm truyền thống trong chế độ không strict, thì các lệnh gọi phương thức this tự động được ràng buộc với globalThis. Trong strict mode, giá trị của this vẫn là undefined.
function Animal() {}
Animal.prototype.speak = function () {
return this;
};
Animal.eat = function () {
return this;
};
const obj = new Animal();
const speak = obj.speak;
speak(); // đối tượng global (trong chế độ không strict)
const eat = Animal.eat;
eat(); // đối tượng global (trong chế độ không strict)
Thông số kỹ thuật
| Specification |
|---|
| ECMAScript® 2027 Language Specification> # sec-class-definitions> |
Tương thích trình duyệt
Xem thêm
- Hướng dẫn Sử dụng classes
class- Biểu thức
class - Hàm
- ES6 In Depth: Classes trên hacks.mozilla.org (2015)