Menu

Toán tử ?? trong JavaScript: đặt giá trị mặc định đúng cách

Cách toán tử ?? chỉ chọn giá trị mặc định khi một giá trị là null hoặc undefined, và vì sao nó tốt hơn || cho phần lớn giá trị mặc định trong thực tế.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Giá trị mặc định thông minh hơn

JavaScript từ lâu đã có một cách viết tắt để đặt giá trị mặc định: value || fallback. Cách này phần lớn chạy được, nhưng có sẵn một lỗi bên trong: nó coi mọi giá trị falsy là bị thiếu. 0, '' và false đều khiến giá trị dự phòng được dùng, kể cả khi chúng chính là câu trả lời thật.

Toán tử nullish coalescing ?? khắc phục điều đó. Nó chỉ dùng giá trị dự phòng khi gặp null hoặc undefined:

0 và '' được giữ lại. null và undefined thì không. Toàn bộ toán tử chỉ có vậy.

Vì sao || chưa đủ

Đây là bug kinh điển mà ?? được thiết kế để ngăn chặn:

Người dùng muốn âm lượng 0 (tắt tiếng) và biệt danh rỗng. Cả hai đều bị ghi đè một cách âm thầm, vì || không phân biệt được "bị thiếu" với "falsy".

Thay bằng ??:

Giờ 0 và '' được giữ nguyên, và chỉ những trường thực sự bị thiếu mới nhận giá trị mặc định. Đây gần như luôn là điều bạn muốn.

Cách hiểu đơn giản

?? chỉ đặt một câu hỏi: vế trái có phải là null hoặc undefined không? Ngoài ra không có gì khác được tính.

Giá trị vế tráileft || right trả vềleft ?? right trả về
nullvế phảivế phải
undefinedvế phảivế phải
0vế phảivế trái (0)
''vế phảivế trái ('')
falsevế phảivế trái (false)
NaNvế phảivế trái (NaN)
đối tượng bất kỳvế tráivế trái

Chọn ?? khi dữ liệu của bạn có những giá trị falsy mang ý nghĩa. Chọn || khi bạn thực sự muốn loại bỏ mọi giá trị falsy (chuỗi rỗng, số đếm bằng 0, v.v.). Trường hợp đó có tồn tại, nhưng hiếm hơn nhiều người nghĩ.

Đoản mạch

Giống || và &&, ?? có đoản mạch (short-circuit). Nếu vế trái không phải nullish, vế phải hoàn toàn không được chạy:

expensiveDefault() chỉ chạy một lần, cho b. Điều này có ích khi giá trị dự phòng là một lời gọi hàm, một request mạng hay bất cứ thứ gì bạn muốn bỏ qua khi không cần đến.

Kết hợp với optional chaining

?? và ?. được giới thiệu cùng nhau trong ES2020 và được thiết kế để phối hợp với nhau. Optional chaining đi qua một đường truy cập có thể bị thiếu và trả về undefined nếu có bước nào không tồn tại; nullish coalescing sau đó điền vào một giá trị mặc định hợp lý:

Nếu không có cặp đôi này, cùng đoạn code đó sẽ là một chuỗi kiểm tra && rườm rà hoặc một khối try/catch. Có chúng, việc truy cập an toàn và giá trị mặc định hợp lý gói gọn trong một dòng.

Phép gán nullish: ??=

a ??= b chỉ gán b cho a khi a là null hoặc undefined. Đây là toán tử gán logic nullish, và nó có đoản mạch: nếu a đã có giá trị thì vế phải không được đánh giá.

Hãy để ý rằng verbose: false và retries: 0 vẫn được giữ nguyên: ??= chỉ điền vào những gì thực sự bị thiếu. Còn ||= thì sẽ ghi đè cả hai.

Độ ưu tiên và quy tắc dấu ngoặc

?? cố ý không cho phép trộn với || hoặc && khi không có dấu ngoặc. Đoạn này sẽ ném ra SyntaxError:

const x = a || b ?? c;   // SyntaxError

Những người thiết kế ngôn ngữ cho rằng độ ưu tiên ở đây đủ mơ hồ để buộc bạn phải viết rõ ràng. Thêm dấu ngoặc là ổn:

Cách nhóm khác nhau, kết quả khác nhau. Dấu ngoặc không phải để trang trí: chúng cho người đọc sau này (và cả trình phân tích cú pháp) biết bạn muốn nói gì.

Không giống tham số mặc định

Tham số mặc định trong khai báo hàm có quy tắc riêng: chúng chỉ được dùng khi đối số là undefined, không phải null. Đó là một khác biệt tinh tế so với ??, vốn coi cả hai như nhau.

Nếu bạn muốn null cũng dùng giá trị dự phòng, hãy dùng ?? trong thân hàm:

Khác biệt nhỏ nhưng dễ vấp khi một API trả về null để báo "chưa đặt giá trị".

Khi nào nên dùng ??

Hãy dùng ?? làm toán tử mặc định của bạn, trừ khi có lý do cụ thể để không dùng. Đây là toán tử khớp với cách phần lớn dữ liệu thực sự hoạt động: 0, '' và false thường là những giá trị thật đáng giữ lại, và chỉ dữ liệu thực sự bị thiếu mới cần giá trị dự phòng. Hãy để dành || cho những trường hợp bạn thực sự muốn thay thế mọi giá trị falsy.

Tiếp theo: lớp

Vậy là xong phần đối tượng và mảng: bạn đã có đủ công cụ để xây dựng và duyệt qua các cấu trúc dữ liệu một cách an toàn. Chương tiếp theo chuyển sang cách tổ chức hành vi: lớp (class), kế thừa và hệ thống prototype nằm bên dưới chúng.

Câu hỏi thường gặp

Toán tử nullish coalescing trong JavaScript là gì?

?? chỉ trả về vế phải khi vế trái là null hoặc undefined. Ngược lại, nó trả về nguyên vế trái. value ?? fallback là cách chuẩn để đặt giá trị mặc định mà không vấp phải những giá trị falsy nhưng hợp lệ như 0 hay ''.

?? và || trong JavaScript khác nhau thế nào?

|| dùng giá trị dự phòng với mọi giá trị falsy: 0, '', false, NaN, null, undefined. ?? chỉ dùng giá trị dự phòng với null và undefined. Nếu 0 hay chuỗi rỗng là giá trị hợp lệ trong dữ liệu của bạn, hãy dùng ??. Nếu bạn thực sự muốn loại bỏ mọi giá trị falsy, || vẫn là lựa chọn đúng.

Có thể kết hợp nullish coalescing với optional chaining không?

Có, và đây là cặp đôi rất phổ biến. user?.settings?.theme ?? 'light' đi qua chuỗi truy cập một cách an toàn, trả về undefined nếu có bước nào bị thiếu, rồi thay bằng 'light'. Hai toán tử này được đưa vào cùng lúc trong ES2020 chính là để hỗ trợ cách viết này.

Toán tử ??= làm gì?

a ??= b chỉ gán b cho a khi a đang là null hoặc undefined. Đây là phép gán logic nullish, cách viết tắt của a = a ?? b, nhưng có đoản mạch: nếu a đã có giá trị thì vế phải thậm chí không được đánh giá. Rất tiện để điền giá trị mặc định còn thiếu vào một đối tượng tùy chọn.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu