Câu 1Trong constructor của class có status = "Chưa xử lý";, đọc this.status thì nhận được gì?
constructor và class field — cách đặt giá trị ban đầu
constructor và class field đặt giá trị ban đầu cho instance: nhận đối số, thứ tự thêm field, giá trị mặc định và chặn giá trị không hợp lệ.
Một ticket hỗ trợ có những giá trị khác nhau ở mỗi ticket, như tiêu đề, và những giá trị ban đầu giống nhau ở mọi ticket, như trạng thái "Chưa xử lý". Nếu viết không cẩn thận, giá trị bị bỏ sót hoặc giá trị không hợp lệ sẽ lọt thẳng vào instance.
Bài này nói về constructor, nơi nhận các giá trị lúc tạo instance, và class field, nơi khai báo các giá trị ban đầu cố định.
Chuẩn hóa đối số trước khi lưu — phần thân constructor
Tiêu đề ticket gửi từ form hỗ trợ đôi khi có khoảng trắng thừa ở đầu hoặc cuối. Nếu bạn ghép chuỗi hiển thị như "Minh: Không đăng nhập được" sau khi đã tạo instance, thì màn hình nào hiển thị nó cũng phải tự viết trim và đoạn ghép chuỗi.
Trong phần thân constructor, vế phải của this.tên = không nhất thiết phải là đối số nguyên dạng: bạn có thể lưu giá trị đã được chuẩn hóa, như this.title = title.trim(), hoặc giá trị ghép từ nhiều đối số. Khi đó, màn hình nào cũng chỉ cần đọc property của instance là có giá trị đã gọn gàng.
class Ticket {
constructor(title, customerName) {
// Chuẩn hóa giá trị nhận vào rồi mới lưu vào this
this.title = title.trim();
this.customerName = customerName;
// Giá trị để hiển thị được ghép từ this sau khi đã chuẩn hóa
this.label = `${this.customerName}: ${this.title}`;
// Nếu viết `${customerName}: ${title}`, khoảng trắng trong tiêu đề sẽ còn nguyên
}
}
// Truyền tiêu đề có khoảng trắng ở hai đầu
const ticket = new Ticket(" Không đăng nhập được ", "Minh");
console.log(ticket.title); // Không đăng nhập được
console.log(ticket.label); // Minh: Không đăng nhập được
Hàng trên đọc đối số title, còn hàng dưới đọc this.title sau khi trim. Nếu bạn chuyển dòng label lên đầu constructor, lúc đó cả this.title lẫn this.customerName đều chưa được gán, nên label sẽ thành undefined: undefined.
Liệt kê các giá trị ban đầu cố định — class field
Ticket nào mới tiếp nhận cũng bắt đầu với trạng thái "Chưa xử lý" và 0 bình luận. Nếu bạn đặt những giá trị ban đầu không liên quan tới đối số như vậy trong constructor bằng this.status = "Chưa xử lý";, chúng sẽ lẫn với các dòng lưu đối số, và muốn biết instance có những giá trị gì thì phải đọc hết phần thân.
Class field (property được viết trong cặp ngoặc nhọn của class nhưng bên ngoài constructor và các phương thức, được thêm vào instance mỗi lần new) chỉ gồm tên và giá trị ban đầu, không có this, như status = "Chưa xử lý";. Khi đọc nó từ phần thân constructor hay từ phương thức, bạn viết this.status giống như mọi property khác.
class Ticket {
// Giá trị ban đầu không phụ thuộc đối số thì đặt thành field
status = "Chưa xử lý";
comments = [];
constructor(title) {
this.title = title;
this.summary = `[${this.status}] ${title}`; // Đọc được giá trị của field
}
}
const first = new Ticket("Không đăng nhập được");
const second = new Ticket("Chưa nhận được hóa đơn");
console.log(first.summary); // [Chưa xử lý] Không đăng nhập được
// comments là các mảng riêng biệt, được tạo mỗi lần new
first.comments.push("Đã nhờ khách khởi động lại");
console.log(first.comments.length); // 1
console.log(second.comments.length); // 0
new, các field được thêm vào trước, sau đó phần thân constructor mới chạy. Trong phần thân, bạn đã đọc được giá trị của field.Bước ① được thực hiện lại mỗi lần new, nên comments = [] cũng là một mảng riêng cho từng instance, và bình luận thêm vào first không xuất hiện trong second. Đó là lý do second.comments.length vẫn là 0.
Viết this. cho field sẽ gây SyntaxError
Nếu bạn viết this.status = "Chưa xử lý"; ở vị trí dành cho field (bên ngoài constructor), bạn sẽ gặp SyntaxError: Unexpected token '.' và không dòng nào được chạy. Thêm let hay const vào cũng gây SyntaxError. Field chỉ gồm tên và giá trị ban đầu.
Quyết định giá trị khi bỏ qua đối số — tham số mặc định
Hầu hết ticket có mức ưu tiên "Thường", và bạn chỉ muốn truyền "Gấp" khi có việc cần xử lý ngay. Nếu vừa viết field priority = "Thường"; vừa thêm this.priority = priority; vào phần thân, thì với ticket được tạo mà không truyền mức ưu tiên, priority lại không phải là "Thường".
Tham số mặc định (cách viết quy định giá trị được dùng khi không truyền đối số) mà bạn đã học ở bài về tham số mặc định và rest parameter cũng dùng được trong constructor dưới dạng priority = "Thường". Đối số bị bỏ qua hoặc là undefined sẽ được thay bằng giá trị mặc định trước khi phần thân chạy, nên this.priority = priority nhận được "Thường".
// Đặt giá trị mặc định trong field, và phần thân cũng gán đối số
class DraftTicket {
priority = "Thường";
constructor(title, priority) {
this.title = title;
this.priority = priority; // priority bên phải là đối số (bỏ qua thì là undefined)
}
}
console.log(new DraftTicket("Không đăng nhập được").priority); // undefined
// Giá trị có thể đổi bằng đối số thì đặt mặc định bằng tham số mặc định
class Ticket {
constructor(title, priority = "Thường") {
this.title = title;
this.priority = priority;
}
}
console.log(new Ticket("Không đăng nhập được").priority); // Thường
console.log(new Ticket("Chưa nhận được hóa đơn", "Gấp").priority); // Gấp
undefined của đối số. Giá trị còn lại cuối cùng là giá trị được gán trong phần thân.Giá trị có thể đổi bằng đối số thì viết thành tham số mặc định, còn giá trị không phụ thuộc đối số thì viết thành field. Với giá trị cần đổi tùy theo đối số, như phí vận chuyển ở Bài tập 2, hãy dùng if trong phần thân để đổi giá trị ban đầu của field. Bảng dưới đây so sánh giá trị cuối cùng của priority theo từng cách viết (kể cả cách không gán trong phần thân), khi chỉ truyền tiêu đề và khi truyền thêm "Gấp".
| Cách viết | Chỉ truyền tiêu đề | Truyền thêm "Gấp" |
|---|---|---|
| Chỉ field (không gán trong thân) | Thường | Thường (bị bỏ qua) |
| Field + this.priority = priority | undefined | Gấp |
| Tham số mặc định priority = "Thường" | Thường | Gấp |
Không cho tạo với giá trị không hợp lệ — throw trong constructor
Nếu có ticket với tiêu đề trống, hoặc với mức ưu tiên không nằm trong các giá trị đã quy định, như "Khẩn", danh sách sẽ hiện ra những dòng trống, và ticket đó cũng bị bỏ sót khi đếm các ticket "Gấp". Kể cả khi bạn viết riêng một hàm kiểm tra sau khi tạo, màn hình nào quên gọi hàm đó vẫn dùng instance không hợp lệ.
Validation lúc tạo (kiểm tra xem giá trị nhận được có thỏa điều kiện hay không ngay tại thời điểm tạo instance) được viết bằng if trong constructor, và nếu không thỏa thì ném ra ngoại lệ bằng throw new Error(...). Khi ngoại lệ xảy ra, biểu thức new không trả về instance nào.
class Ticket {
constructor(title, priority = "Thường") {
// Chặn giá trị không hợp lệ bằng ngoại lệ trước khi lưu vào this
if (title.trim() === "") {
throw new Error("Tiêu đề đang trống");
}
if (priority !== "Thường" && priority !== "Gấp") {
throw new Error(`Mức ưu tiên phải là Thường hoặc Gấp: ${priority}`);
}
this.title = title.trim();
this.priority = priority;
}
}
try {
const ticket = new Ticket("Không đăng nhập được", "Khẩn");
console.log(ticket.title); // Dòng này không được chạy
} catch (error) {
console.log(error.message); // Mức ưu tiên phải là Thường hoặc Gấp: Khẩn
}
const ticket = new Ticket(...)— vế phải ném ra ngoại lệ,ticketkhông nhận được gìconsole.log(ticket.title)— không được chạy
- Kiểm tra tiêu đề — không trống nên qua
- Kiểm tra mức ưu tiên — là
"Khẩn"nênthrow this.title = ...— từ đây trở xuống không được chạy
error.message— "Mức ưu tiên phải là Thường hoặc Gấp: Khẩn"
new, bỏ qua phần còn lại của try và tới catch. Instance không hợp lệ không được gán cho biến nào cả.Khi đặt phần kiểm tra trong constructor, dù có bao nhiêu chỗ viết new Ticket(...) thì cũng không ticket nào được tạo mà không qua kiểm tra. Nếu đưa giá trị nhận được vào thông báo của ngoại lệ, phía catch còn có thể hiển thị giá trị nào không hợp lệ.
Phép gán sau khi tạo không được kiểm tra
Phần kiểm tra trong constructor chỉ chạy lúc new. Nếu sau khi tạo bạn gán ticket.priority = "Khẩn";, sẽ không có lỗi nào và giá trị bị thay đổi luôn. Cách kiểm tra giá trị ở mỗi lần gán sẽ được giới thiệu trong bài về getter và setter.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Class chỉ có field priority = "Thường"; và không gán trong phần thân. Nếu truyền "Gấp" thì sao?
Câu 3Trong try, constructor ném ra ngoại lệ ở const ticket = new Ticket(""). Điều gì xảy ra?