Câu 1Viết let couponCode; và đọc nó mà không gán giá trị. Giá trị của biến là gì?
null và undefined — Đọc An Toàn Với ?. và ??
Phân biệt null và undefined, rồi học cách đọc qua một bước lồng nhau bị thiếu và đặt mặc định an toàn.
Hiển thị dữ liệu đơn hàng từ một server, bạn sẽ gặp một địa chỉ chưa từng được điền, hay một phương thức thanh toán chưa được chọn. JavaScript có hai giá trị cho trạng thái "không có gì ở đây" này — undefined và null — và một lần đọc thất bại làm dừng chương trình với TypeError.
Bài này nói về khác biệt giữa hai giá trị đó, đọc qua một bước thiếu mà không dừng lại, và áp dụng giá trị mặc định khi có gì đó bị thiếu.
2 Giá Trị Nghĩa Là Không Có Gì Ở Đó — undefined và null
undefined (giá trị JavaScript tự động điền vào khi chưa có gì được gán) xuất hiện tự nó — bạn không cần tự gán nó. Bạn đã thấy nó trả về từ việc đọc quá cuối một mảng hay một key object chưa đăng ký, và một biến chỉ mới được khai báo cũng thuộc nhóm này.
- Một biến chỉ mới khai báo, chưa từng gán giá trị, như
let couponCode; - Một key chưa đăng ký trên một object, như
order.customer(đã thấy trước đó) - Một index ngoài phạm vi trên mảng 2 phần tử, như
tags[5](đã thấy trước đó)
null (giá trị người viết cố tình gán để báo hiệu trống rỗng), ngược lại, không bao giờ tự xuất hiện. Khi bạn muốn chính sự thiếu vắng của một giá trị trở thành dữ liệu — "chưa chọn địa chỉ vận chuyển nào," "chưa áp dụng mã giảm giá nào" — bạn tự gán null.
JSON trả về từ một API cũng có thể mang null để báo hiệu một trường đang trống.
Vì chúng là các giá trị khác nhau, === trả về false khi so sánh chúng. ==, tuy nhiên, coi hai cái là giống nhau và trả về true.
Khi viết một kiểm tra, hoặc cố ý biết rõ bạn đang mong đợi cái nào và chỉ kiểm tra bên đó, như === null, hoặc giao hẳn cho ?. và ??, sẽ nói từ đây trở đi.
let couponCode; // chỉ mới khai báo, chưa gán giá trị
console.log(couponCode); // undefined
const order = { orderId: "ORD-1105", total: 12800 };
console.log(order.customer); // undefined (một key chưa đăng ký)
const tags = ["Limited", "Preorder"];
console.log(tags[5]); // undefined (một index ngoài phạm vi)
// null được người viết tự gán
let shippingAddress = null; // chưa chọn địa chỉ vận chuyển nào
console.log(shippingAddress); // null
console.log(shippingAddress === undefined); // false (chúng là các giá trị khác nhau)
console.log(shippingAddress == undefined); // true (== coi chúng giống nhau)
Không Dừng Lại Khi Một Bước Lồng Nhau Bị Thiếu — Optional Chaining ?.
Dữ liệu trả về từ một API không phải lúc nào cũng đầy đủ mọi trường. Code đọc sâu, như order.customer.address.city, dừng lại ngay khi dù chỉ một đơn hàng thiếu address ở giữa chuỗi.
Đây là lúc bạn muốn chính việc đọc không thất bại — mà tiếp tục như "không có gì ở đó" thay vào đó.
Khi order.customer.address là undefined, cố đọc .city sau nó sẽ ném ra TypeError: Cannot read properties of undefined (reading 'city'). Cấp đọc thứ 1 trót lọt, nhưng nó dừng ở cấp thứ 2.
Đó là lý do có optional chaining (toán tử ?., trả về undefined mà không đọc tiếp nếu bên trái là null hoặc undefined). Viết address?.city, và .city chỉ được đọc khi address có giá trị — nếu không, việc đọc dừng ở đó và trả về undefined.
Khi truyền một key qua [], viết nó là ?.[field].
const order = {
orderId: "ORD-1105",
customer: { name: "Ryan Cole" },
};
// address bị thiếu, nên đọc city sau nó sẽ dừng lại
// console.log(order.customer.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')
// Chèn ?. và việc đọc dừng lại, trả về undefined
console.log(order.customer.address?.city); // undefined
// Nơi có giá trị, nó tiếp tục như bình thường
console.log(order.customer.name); // Ryan Cole
// Truyền một key qua biến với ?.[]
const field = "city";
console.log(order.customer.address?.[field]); // undefined
Chỉ Dùng Nó Nơi Một Trường Hợp Lý Có Thể Bị Thiếu
Đặt ?. ở mọi bước, và ngay cả một tên key gõ nhầm cũng trượt qua thành undefined mà không báo lỗi. Bỏ qua nó ở các trường chắc chắn sẽ có mặt, và chỉ dùng nó ngay trước một trường hợp lý có thể thiếu — như vậy các lỗi bạn thực sự muốn bắt vẫn dừng chương trình.
Thiết Lập Giá Trị Mặc Định — ?? vs ||
Thay vì hiện một undefined trơ trọi khi một trường bị thiếu, thường thì bạn sẽ muốn hiển thị một giá trị mặc định — "Guest," "20 items."
Dùng || cho việc này, tuy nhiên, đôi khi một giá trị thực sự có ở đó lại bị thay bằng mặc định. Số lượng tồn kho 0, hay một trường ghi chú trống, là ví dụ.
null và undefined gộp lại gọi là nullish. Dùng Nullish coalescing operator (toán tử ??, chỉ trả về bên phải khi bên trái là nullish), và 0, một chuỗi rỗng, hay false giữ nguyên đúng như chúng vốn có.
|| trả về bên phải bất cứ khi nào bên trái falsy, nên nó không phân biệt được "không có giá trị" với "giá trị là 0". Khi mục tiêu của bạn là áp dụng mặc định, ?? tránh việc thay thế ngoài ý muốn đó.
0. ?? chỉ thay null và undefined, nên 0 giữ nguyên là giá trị.| Giá Trị Bên Trái | || Trả Về Gì | ?? Trả Về Gì |
|---|---|---|
| null | giá trị mặc định | giá trị mặc định |
| undefined | giá trị mặc định | giá trị mặc định |
| 0 | giá trị mặc định | vẫn là 0 |
| chuỗi rỗng | giá trị mặc định | vẫn là chuỗi rỗng |
| false | giá trị mặc định | vẫn là false |
const settings = { displayName: null, itemsPerPage: 0 };
// ?? chỉ trả về bên phải khi bên trái là null hoặc undefined
console.log(settings.displayName ?? "Guest"); // Guest
console.log(settings.itemsPerPage ?? 20); // 0 (giữ nguyên)
// || trả về bên phải bất cứ khi nào bên trái falsy, nên ngay cả 0 cũng bị thay
console.log(settings.itemsPerPage || 20); // 20
// Một key chưa đăng ký quay về mặc định theo cả hai cách
console.log(settings.language ?? "en"); // en
console.log(settings.language || "en"); // en
Lưu ý viết ?? và || cạnh nhau mà không có dấu ngoặc, như displayName ?? "Guest" || "Anonymous", là lỗi cú pháp — JavaScript không thể quyết định cái nào tính trước. Để dùng cả hai, tách chúng bằng dấu ngoặc, như (displayName ?? "Guest") || "Anonymous".
Đừng Để Mặc Định Xóa Mất 0 hoặc Chuỗi Rỗng
Số lượng tồn kho 0, tỷ lệ giảm giá 0, một ghi chú trống — tất cả đều tính là "có giá trị". Áp dụng mặc định bằng ||, và vì chúng falsy, chúng bị thay bằng mặc định, tạo ra sự không khớp như một mặt hàng hết hàng lại hiện thành còn hàng. Dùng ?? khi bạn chỉ quan tâm một giá trị có tồn tại hay không.
Không Gọi Thứ Không Tồn Tại — Lệnh Gọi ?.()
Trong xử lý đơn hàng, đôi khi bạn sẽ cất "làm gì khi thành công" và "làm gì khi thất bại" bên trong một object, gọi chúng khi cần. Gọi một trường chưa được thiết lập gì cả, tuy nhiên, và nó dừng lại với TypeError: handlers.onError is not a function — nên bạn cần một cách để chỉ gọi nó khi nó thực sự có ở đó.
Hàm nói ở bài 3 cũng có thể sống bên trong một object như giá trị của một property. () => ... ở ví dụ tiếp theo là cú pháp để tạo một hàm ngay tại chỗ — cú pháp đó sẽ nói riêng ở nhóm cú pháp, nên ở đây chỉ tập trung vào kết quả thay đổi thế nào tùy vào việc property có chứa hàm hay không.
Gọi một property có thể chứa hàm dưới dạng handlers.onError?.(), và khi onError là null hoặc undefined, lệnh gọi bị bỏ qua và undefined trả về thay vào đó.
undefined mà không gọi gì nếu nó null. Gọi nó không có ?. thì dừng lại với TypeError.const handlers = {
onSuccess: () => console.log("Order confirmed"),
onError: null,
};
// Có một hàm ở đó, nên nó được gọi
handlers.onSuccess?.(); // Order confirmed
// Nó là null, nên lệnh gọi bị bỏ qua và undefined trả về
console.log(handlers.onError?.()); // undefined
// Gọi mà không có ?. sẽ dừng chương trình
// handlers.onError();
// TypeError: handlers.onError is not a function
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Khi bên trái của ?. là null hoặc undefined, chuyện gì xảy ra với phần còn lại của chuỗi?
Câu 3Với const discountRate = 0;, discountRate || 5 và discountRate ?? 5 mỗi cái trả về gì?