Câu 1Parcel có static count = 0;. Đọc first.count trên instance first thì nhận được gì?
Instance member và static — giá trị và phương thức thuộc về class
static đặt giá trị và phương thức lên chính class: bộ đếm bằng static field, factory method từ JSON, hằng số dùng chung và instance member.
Để đánh số vận đơn theo thứ tự tạo, bạn cần lưu ở đâu đó số lượng vận đơn đã tạo. Người nhận thì mỗi vận đơn một khác, nhưng số lượng đã tạo hay giới hạn trọng lượng chỉ cần có một giá trị dùng chung cho mọi vận đơn.
Bài này nói về static, cách đặt giá trị và phương thức lên chính class thay vì lên instance.
Đếm số lượng vận đơn — static field
Giả sử bạn muốn đánh số vận đơn theo thứ tự được tạo. Nếu bạn viết class field count = 0;, tăng nó bằng this.count += 1 trong constructor rồi dùng làm số vận đơn, thì vận đơn nào cũng mang số 1, vì field được khởi tạo lại bằng 0 ở mỗi lần new.
Static field (property được viết trong class với static ở phía trước, chỉ có một bản duy nhất nằm trên chính class) được đọc và ghi qua tên class, như Parcel.count.
Ngược lại, những property mà mỗi instance giữ riêng như this.number, cùng các phương thức viết không có static, được gọi là instance member.
class Parcel {
// Số lượng vận đơn đã tạo đến giờ, chỉ có một giá trị trên class
static count = 0;
constructor(recipient) {
Parcel.count += 1; // Tăng count của class thêm 1
this.number = Parcel.count; // Dùng số lượng tại thời điểm này làm số vận đơn
this.recipient = recipient;
}
}
// Tạo 2 vận đơn
const first = new Parcel("Minh");
const second = new Parcel("Linh");
console.log(first.number, second.number); // 1 2
console.log(Parcel.count); // 2
console.log(first.count); // undefined
static count— thành 1 saunewđầu tiên, 2 saunewthứ hai- Đọc và ghi bằng
Parcel.count
number— 1 (count lúc được tạo)recipient— "Minh"- Không có
count—first.countlàundefined
number— 2 (count lúc được tạo)recipient— "Linh"
count chỉ có một, nằm trong khung của class, không có trong khung của instance. Đọc nó từ instance sẽ ra undefined.Cả hai lần new đều tăng cùng một count trong khung của class thêm 1, rồi chép giá trị lúc đó vào number của riêng instance mới. Dù sau đó count tăng lên 2, first.number vẫn là 1, giá trị nó nhận lúc được tạo.
Tạo vận đơn từ JSON — static method
Khi tải lại một vận đơn đã lưu, bạn sẽ nhận được một chuỗi JSON chứa người nhận và trọng lượng. Nếu màn hình nào tải vận đơn cũng tự viết JSON.parse và new Parcel(...), thì mỗi khi tên key trong JSON thay đổi, bạn phải sửa tất cả các màn hình đó.
Lúc tạo vận đơn từ JSON thì chưa có instance nào để gọi phương thức. Static method (phương thức được định nghĩa với static, gọi qua tên class mà không cần tạo instance) có thể gọi dưới dạng Parcel.fromJSON(text). Static method tạo và trả về một instance như fromJSON được gọi là factory method.
class Parcel {
constructor(recipient, weightKg) {
this.recipient = recipient;
this.weightKg = weightKg;
}
label() {
return `${this.recipient} (${this.weightKg} kg)`;
}
// Nhận chuỗi JSON, tạo instance của Parcel rồi trả về
static fromJSON(text) {
const data = JSON.parse(text);
return new Parcel(data.recipient, data.weightKg);
}
}
const text = '{"recipient":"Minh","weightKg":2}';
const parcel = Parcel.fromJSON(text); // Gọi qua tên class
console.log(parcel.label()); // Minh (2 kg)
JSON.parse, object có property nhưng chưa có phương thức. Chỉ sau khi đi qua new, nó mới thành instance gọi được label().Dòng cuối của fromJSON là new Parcel(...), nên phần thân constructor vẫn chạy như bình thường. Nếu bạn đặt các phép kiểm tra bằng throw trong constructor, vận đơn tải lại từ JSON cũng đi qua đúng những phép kiểm tra như vận đơn nhập trên màn hình.
Không gọi được fromJSON từ instance
Nếu gọi từ instance như parcel.fromJSON(text), bạn sẽ gặp lỗi TypeError: parcel.fromJSON is not a function. Khác với label(), phương thức có static chỉ gọi được qua tên class. Cơ chế phía sau sẽ được giải thích trong bài về prototype.
Giá trị không đổi cho mọi vận đơn — hằng số static
Giới hạn trọng lượng 30 kg được dùng cả trong phần hướng dẫn của form nhập liệu (hiển thị trước khi tạo vận đơn) lẫn trong phép tính trọng lượng còn lại của từng vận đơn. Nếu đặt giới hạn này thành field của instance, bạn sẽ phải tạo một vận đơn trống, chưa có người nhận và trọng lượng, chỉ để hiển thị hướng dẫn trên form.
Hằng số static (static field giữ một giá trị không thay đổi trong suốt chương trình) được đặt tên toàn chữ in hoa, các từ nối bằng _, như static MAX_WEIGHT_KG = 30;. Dù ở ngoài class hay trong phương thức, bạn đều đọc nó qua tên class: Parcel.MAX_WEIGHT_KG.
class Parcel {
// Giới hạn dùng chung cho mọi vận đơn, đặt một lần trên class bằng static
static MAX_WEIGHT_KG = 30;
constructor(weightKg) {
this.weightKg = weightKg;
}
// Còn cho thêm được bao nhiêu kg trước khi chạm giới hạn
remainingKg() {
return Parcel.MAX_WEIGHT_KG - this.weightKg;
// Nếu viết this.MAX_WEIGHT_KG - this.weightKg thì sẽ trả về NaN
}
}
// Đọc được ngay cả khi chưa có instance nào
console.log(`Tối đa ${Parcel.MAX_WEIGHT_KG} kg mỗi kiện`); // Tối đa 30 kg mỗi kiện
const parcel = new Parcel(12);
console.log(parcel.remainingKg()); // 18
Cột phải ra NaN vì this bên trong phương thức trỏ tới instance, mà hằng số static thì không nằm trên instance. undefined - 12 không báo lỗi, nên bạn chỉ phát hiện ra khi thấy trọng lượng còn lại hiển thị sai.
Tên viết hoa vẫn không ngăn được phép gán
Tên viết hoa chỉ là một quy ước, mang ý nghĩa "giá trị không thay đổi"; nếu bạn gán Parcel.MAX_WEIGHT_KG = 50; thì giá trị vẫn bị thay đổi. Nếu chỉ viết getter như static get MAX_WEIGHT_KG() { return 30; }, phép gán sẽ báo lỗi TypeError (getter được giới thiệu ở bài tiếp theo).
Quyết định nơi đặt giá trị — static hay instance
Khi đã biết static, bạn có thể thấy tiện nếu đặt cả giá trị như người nhận lên class để đọc ở một chỗ duy nhất qua Parcel.recipient. Khi thử nhanh với chỉ một vận đơn, dù đặt ở static hay ở this thì người nhận hiển thị vẫn như nhau, nên bạn sẽ không nhận ra sự khác biệt.
Hãy quyết định nơi đặt giá trị dựa vào việc nó có khác nhau ở từng mục hay không. Giá trị đặt vào this tồn tại riêng trong từng instance được tạo bằng new, còn giá trị static chỉ có một trên class, dù bạn tạo bao nhiêu vận đơn.
// Đặt người nhận ở static
class SharedParcel {
static recipient = "";
constructor(recipient) { SharedParcel.recipient = recipient; }
label() { return `Người nhận: ${SharedParcel.recipient}`; }
}
// Đặt người nhận ở instance
class Parcel {
constructor(recipient) { this.recipient = recipient; }
label() { return `Người nhận: ${this.recipient}`; }
}
const sharedFirst = new SharedParcel("Minh");
new SharedParcel("Linh");
console.log(sharedFirst.label()); // Người nhận: Linh
const first = new Parcel("Minh");
new Parcel("Linh");
console.log(first.label()); // Người nhận: Minh
sharedFirst không có người nhận của riêng nó, và label() đọc SharedParcel.recipient tại thời điểm gọi, nên kết quả là "Linh", giá trị được gán sau cùng. Bảng dưới đây tóm tắt cách viết và cách dùng các member của vận đơn trong bài này.
| Cần lưu gì | Cách viết và cách dùng | Ví dụ trong bài |
|---|---|---|
| Giá trị riêng mỗi mục | Đặt vào this, đọc từ instance | number / recipient / weightKg |
| Phương thức đọc giá trị riêng | Không có static, gọi từ instance | label() / remainingKg() |
| Một giá trị chung | Có static, đọc qua tên class | count / MAX_WEIGHT_KG |
| Xử lý không cần instance | Có static, gọi qua tên class | fromJSON(text) |
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Nếu gọi static method từ instance parcel như parcel.fromJSON(text) thì sao?
Câu 3Class đặt người nhận ở static. Nếu tạo vận đơn cho "Minh" rồi cho "Linh", label() của vận đơn đầu hiển thị người nhận nào?