Hai cách nói "không có gì"
Phần lớn ngôn ngữ chỉ có một giá trị để biểu thị sự vắng mặt. JavaScript có tới hai: null và undefined. Chúng giống nhau đủ để người mới bắt đầu nhầm lẫn, và khác nhau đủ để làm khó cả lập trình viên có kinh nghiệm. Bỏ ra mười phút để hiểu rõ sự khác biệt này là rất đáng.
Nói ngắn gọn:
undefinedlà thứ JavaScript trả về khi một thứ gì đó không tồn tại.nulllà thứ bạn viết khi muốn nói "cố ý để trống".
Hãy để ý quy luật: mỗi undefined ở trên xuất hiện vì JavaScript không tìm thấy giá trị. Còn null xuất hiện vì có người đã viết nó ra.
undefined đến từ đâu
undefined xuất hiện trong một vài tình huống cụ thể, tất cả đều là biến thể của "không có giá trị nào":
Trong mọi trường hợp, JavaScript đi tìm một giá trị và không thấy gì cả. undefined là cách engine nói "tôi đã tìm, nhưng không có giá trị nào".
Bạn có thể tự gán undefined (let x = undefined;), nhưng không nên. Hãy để nó tiếp tục là tín hiệu cho "JavaScript không tìm thấy gì". Dùng null khi chính bạn là người đưa ra quyết định.
null đến từ đâu
null chỉ xuất hiện khi có người viết nó ra. Đó chính là mục đích của nó: một dấu hiệu có chủ ý.
Các API DOM dùng null rất nhiều: document.getElementById("missing") trả về null chứ không phải undefined, vì trình duyệt đang nói rõ với bạn "tôi đã tìm, và không có phần tử nào". JSON.parse("null") cũng trả về null, vì JSON không có undefined.
Cách nghĩ dễ nhớ: undefined là sự vắng mặt mặc định, null là sự vắng mặt được chọn.
Điểm lạ của typeof
Đây là điểm lạ nổi tiếng nhất:
Việc typeof null trả về "object" là một bug từ năm 1995, không thể sửa mà không làm hỏng các trang web đang có, nên nó được giữ lại mãi mãi. null không phải là đối tượng: nó là kiểu nguyên thủy, giống như undefined, số, chuỗi và boolean. Kết quả của typeof chỉ đơn giản là nói sai về nó.
Hệ quả thực tế: typeof vô dụng khi cần phát hiện null. Hãy so sánh trực tiếp:
Hoặc thường gặp hơn: kiểm tra cả hai cùng lúc, như ở phần tiếp theo.
Kiểm tra cả hai: cách viết == null
Phần lớn thời gian bạn không quan tâm mình đang có giá trị vắng mặt nào, bạn chỉ muốn biết có thứ gì bị thiếu trước khi dùng nó. Cách kiểm tra quen thuộc là so sánh lỏng với null:
value == null đúng với đúng null và undefined, và sai với mọi giá trị khác, kể cả 0, "" và false, thường cũng là điều bạn muốn. Đây là tình huống duy nhất mà == được ưu tiên hơn ===. Các linter đều biết điều này và cho phép nó.
Nếu muốn viết rõ ràng, value === null || value === undefined có cùng ý nghĩa và cũng dễ đọc.
Toán tử nullish: ?? và ?.
Có hai toán tử (operator) được thêm vào riêng để làm việc với null và undefined dễ hơn. Cả hai đều coi hai giá trị này như nhau và không đụng tới mọi giá trị khác.
Nullish coalescing (??) chỉ đưa ra giá trị dự phòng khi vế trái là null hoặc undefined:
So sánh với ||: toán tử này sẽ thay 0 bằng 3 vì 0 là falsy. ?? chặt chẽ hơn: nó chỉ có tác dụng với hai giá trị nullish.
Optional chaining (?.) dừng việc đánh giá cả chuỗi truy cập và trả về undefined khi gặp null hoặc undefined:
Hai toán tử này ra đời vì câu hỏi "giá trị này có phải nullish không?" xuất hiện quá thường xuyên. Chúng được trình bày chi tiết ở các bài sau.
Tham số mặc định chỉ áp dụng cho undefined
Một quy tắc tinh tế nhưng quan trọng: giá trị mặc định của tham số chỉ được dùng khi đối số là undefined, không phải null.
Truyền null được hiểu là "tôi cố ý chọn không có giá trị" và được giữ nguyên. Nếu bạn muốn null cũng dùng giá trị dự phòng, hãy dùng ?? bên trong hàm:
Sự khác biệt này khiến nhiều người vấp thường xuyên. Giá trị mặc định xử lý đối số bị thiếu; ?? xử lý đối số nullish.
JSON và undefined bị mất
JSON có null nhưng không có undefined. Điều đó dẫn tới những bất ngờ khó thấy khi bạn chuyển dữ liệu sang JSON:
Trường age biến mất hoàn toàn: JSON.stringify bỏ qua các thuộc tính có giá trị undefined. null vẫn còn vì JSON hỗ trợ nó. Chuyển một đối tượng sang JSON rồi đọc ngược lại là cách thường gặp khiến các thuộc tính undefined lặng lẽ biến mất.
Trong mảng, undefined trở thành null:
Nếu bạn đang thiết kế payload cho API, hãy ưu tiên null thay vì undefined cho các trường "không có giá trị". null sẽ không bị mất trên đường đi.
Khi nào dùng giá trị nào
Một quy ước hợp lý cho code của bạn:
- Để
undefinedmang nghĩa "không được cung cấp": đối số bị thiếu, biến chưa gán, thuộc tính không tồn tại. Đừng tự tay gánundefined. - Dùng
nullkhi bạn muốn nói rõ "chỗ này cố ý để trống": người dùng đã đăng xuất, tùy chọn chưa được chọn, ô nhập liệu đã bị xóa. - Chấp nhận cả hai ở ranh giới API (dùng
== nullhoặc??), nhưng hãy nhất quán về giá trị mà bạn tạo ra.
Một số hướng dẫn phong cách (kể cả của TypeScript) bỏ hẳn null và chỉ dùng undefined. Cách đó cũng có lý: một giá trị thì đơn giản hơn hai. Hãy chọn một quy tắc cho mỗi dự án và áp dụng ở mọi nơi.
Tiếp theo: ép kiểu
null và undefined hoạt động theo những cách riêng khi JavaScript chuyển chúng thành số, chuỗi hoặc boolean: Number(null) là 0, nhưng Number(undefined) lại là NaN, và kiểu bất đối xứng đó gây ra bug thật. Ép kiểu (type coercion) là chủ đề tiếp theo, và khi đã thấy đầy đủ các quy tắc, nhiều điểm kỳ quặc của JavaScript sẽ không còn khó hiểu nữa.
Câu hỏi thường gặp
null và undefined trong JavaScript khác nhau thế nào?
undefined nghĩa là một giá trị chưa được gán: JavaScript trả về nó cho biến chưa gán, đối số không được truyền hoặc thuộc tính không tồn tại trên đối tượng. null là cách nói rõ ràng 'ở đây không có giá trị' mà bạn gán một cách có chủ ý. JavaScript không bao giờ tự sinh ra null; bạn phải tự viết nó.
Làm sao kiểm tra cả null và undefined cùng lúc?
Dùng value == null. So sánh lỏng coi null và undefined bằng nhau nhưng không bằng bất kỳ giá trị nào khác, nên x == null chỉ đúng với đúng hai giá trị này. Đây là chỗ duy nhất mà dùng == thay cho === vừa phổ biến vừa an toàn.
Vì sao typeof null trả về 'object'?
Đây là một bug có từ phiên bản đầu tiên của JavaScript và không thể sửa mà không làm hỏng web. typeof null trả về 'object' dù null là kiểu nguyên thủy. Muốn kiểm tra riêng null, hãy so sánh trực tiếp: value === null.
Nên dùng null hay undefined trong code của mình?
Để undefined mang nghĩa 'không có gì được truyền vào' và dùng null khi bạn muốn nói rõ 'chỗ này cố ý để trống'. Nhiều codebase (và hướng dẫn phong cách của TypeScript) bỏ hẳn null và chỉ dùng undefined. Hãy chọn một quy ước cho mỗi dự án và giữ đúng quy ước đó.