Toán tử là cách biểu thức thực hiện công việc
Mọi dòng JavaScript không tầm thường đều được xây từ các toán tử (operator): những ký hiệu nhận một hoặc hai giá trị và tạo ra một giá trị mới. + cộng, === so sánh, && kết hợp boolean, ? : chọn giữa hai giá trị. Phần lớn chúng quen thuộc từ các ngôn ngữ khác; một số ít có những điểm lạ riêng của JavaScript mà bạn nên biết ngay từ đầu.
Chúng ta sẽ đi qua từng nhóm, kết thúc bằng những toán tử bạn ít dùng nhất nhưng lại quan trọng khi cần đến.
Toán tử số học
Các phép tính cơ bản hoạt động đúng như bạn nghĩ:
Vài điều đáng lưu ý:
/luôn là phép chia số thực.7 / 2là3.5, không phải3. Hãy dùngMath.floor(7 / 2)hoặcMath.trunc(7 / 2)nếu bạn muốn số nguyên.%là phép lấy phần dư, không phải phép modulo đúng nghĩa toán học. Kết quả giữ dấu của toán hạng bên trái:-7 % 3là-1, không phải2.+mang hai nghĩa. Nếu một trong hai vế là chuỗi, nó sẽ nối chuỗi thay vì cộng:"3" + 1là"31". Phần này sẽ được nói kỹ hơn ngay sau đây.
Tăng và giảm
Khác biệt giữa count++ và ++count chỉ quan trọng khi bạn dùng giá trị của biểu thức trên cùng một dòng. Khi đứng riêng thành một câu lệnh, cả hai làm cùng một việc. Phần lớn hướng dẫn phong cách ưu tiên count += 1 cho rõ ràng.
Toán tử + có hai tính cách
Điều này khiến ai cũng vấp ít nhất một lần:
Nếu một trong hai toán hạng là chuỗi, + trở thành phép nối chuỗi và chuyển vế còn lại thành chuỗi. Mọi toán tử số học khác lại làm ngược lại: chúng chuyển chuỗi thành số:
Bài học rút ra: khi tạo chuỗi, hãy ưu tiên template literal (`price: ${5}`). Khi tính toán, hãy đảm bảo đầu vào thực sự là số: Number(x) hoặc parseInt(x, 10) chuyển đổi một cách rõ ràng.
Toán tử so sánh
Phép so sánh trả về boolean. Có hai kiểu: nghiêm ngặt và lỏng.
=== và !== là so sánh nghiêm ngặt: cùng giá trị và cùng kiểu. == và != ép kiểu trước khi so sánh, dẫn tới những bất ngờ như null == undefined là true hay [] == false là true. Hãy mặc định dùng === và !==. Ngoại lệ phổ biến duy nhất là x == null, một cách viết tắt tiện lợi cho câu hỏi "x là null hoặc undefined phải không?"
Các toán tử so sánh thứ tự hoạt động như bạn nghĩ với số, và theo thứ tự bảng chữ cái với chuỗi:
So sánh chuỗi dựa trên mã ký tự, nên có phân biệt chữ hoa chữ thường. Nếu cần sắp xếp theo cách tự nhiên với con người, hãy dùng String.prototype.localeCompare.
Toán tử logic
&& (AND), || (OR) và ! (NOT) kết hợp các giá trị boolean, nhưng chúng thú vị hơn so với đại số boolean thuần túy.
Điểm đặc biệt: && và || không trả về true hay false. Chúng trả về một trong các toán hạng của mình. && trả về giá trị falsy đầu tiên, hoặc giá trị cuối cùng nếu tất cả đều truthy. || trả về giá trị truthy đầu tiên, hoặc giá trị cuối cùng nếu tất cả đều falsy.
Đó là lý do bạn sẽ thấy cách viết như const displayName = user.name || "Guest": chọn thứ đầu tiên không rỗng. Cách này ngắn gọn, nhưng hãy cẩn thận: || coi 0, "" và false là lý do để dùng giá trị dự phòng. Nếu đó là những giá trị hợp lệ với bạn, hãy dùng ?? (ở phần dưới).
Cả hai toán tử còn có đoản mạch (short-circuit): nếu vế trái đã quyết định được kết quả, vế phải sẽ không bao giờ chạy.
Toán tử nullish coalescing ??
?? giống || nhưng chỉ có tác dụng với null hoặc undefined, không phải với 0, "" hay false.
Dùng ?? khi một giá trị hợp lệ có thể là falsy: số đếm, chuỗi rỗng, false được gán rõ ràng. Dùng || khi mọi giá trị falsy đều mang nghĩa "bị thiếu". Trong code hiện đại, ?? là lựa chọn mặc định an toàn hơn cho các giá trị tùy chọn có giá trị mặc định khác null.
Toán tử gán
= dùng để gán. Các dạng kết hợp gộp phép gán với một toán tử khác:
Cũng có các toán tử gán logic: ||=, &&= và ??=, chỉ gán khi giá trị hiện tại thỏa mãn một điều kiện:
Chúng rất tiện để điền giá trị mặc định mà không cần những câu lệnh if dài dòng.
Toán tử ba ngôi
condition ? a : b là phiên bản biểu thức của if/else. Nó trả về a khi điều kiện là truthy, ngược lại trả về b:
Toán tử ba ngôi rất hợp để chọn nhanh một giá trị. Nhưng chúng trở nên khó đọc ngay khi bạn lồng chúng vào nhau: nếu thấy mình đang viết a ? b : c ? d : e, hãy chuyển sang if/else hoặc một đối tượng tra cứu.
typeof và instanceof
typeof trả về một chuỗi mô tả kiểu của toán hạng:
Hai cái bẫy nên ghi nhớ: typeof null là "object" (một bug từ năm 1995 nay đã thành vĩnh viễn), và mảng cũng báo là "object". Hãy dùng Array.isArray(x) cho mảng và x === null cho null.
instanceof kiểm tra xem một đối tượng có được tạo từ một hàm khởi tạo cụ thể không:
Spread và rest dùng chung ...
Bạn sẽ thấy ... ở hai chỗ. Với vai trò spread, nó trải một iterable ra thành từng phần tử:
Với vai trò rest, nó gom nhiều giá trị vào một mảng duy nhất, thường là trong tham số hàm hoặc khi phân rã:
Cùng cú pháp, hai việc ngược nhau. Spread mở ra; rest gom lại. Ngữ cảnh quyết định đó là loại nào: trong lời gọi hàm hoặc literal, nó là spread; trong danh sách tham số hoặc mẫu phân rã, nó là rest.
Độ ưu tiên toán tử (khi nghi ngờ, hãy thêm ngoặc)
Các toán tử có độ ưu tiên quyết định toán tử nào chạy trước khi bạn kết hợp chúng. Phép nhân được ưu tiên hơn phép cộng, phép so sánh được ưu tiên hơn phép logic, v.v.:
Bảng đầy đủ rất dài, và không ai nhớ hết. Một thói quen đơn giản giải quyết được 99% trường hợp: khi kết hợp các toán tử mà không chắc chắn về thứ tự, hãy thêm dấu ngoặc. Code dễ đọc hơn và không phụ thuộc vào trí nhớ của người đọc.
Toán tử bitwise (hiếm khi là thứ bạn cần)
Để đầy đủ: &, |, ^, ~, <<, >> và >>> hoạt động trên dạng nhị phân của số nguyên. Bạn sẽ gặp chúng trong code đồ họa, các giao thức cấp thấp và đôi khi trong API dùng bitmask làm cờ.
Một mẹo phổ biến nhưng đáng ngờ: n | 0 cắt một số thành số nguyên 32 bit, từng bị lạm dụng như một phiên bản Math.trunc nhanh hơn. Đừng làm vậy: Math.trunc rõ ràng hơn và dùng được với cả những số nằm ngoài phạm vi 32 bit.
Tiếp theo: if/else
Toán tử tạo ra giá trị; if/else dùng những giá trị đó để chọn nhánh code sẽ chạy. Phần lớn những gì bạn làm với các toán tử so sánh và logic ở trên là đưa kết quả của chúng vào câu lệnh điều kiện, và đó chính là nội dung của trang tiếp theo.
Câu hỏi thường gặp
Các toán tử chính trong JavaScript là gì?
JavaScript có toán tử số học (+, -, *, /, %, **), so sánh (===, !==, <, >), logic (&&, ||, !), gán (=, +=, -=) và một vài toán tử đặc biệt như toán tử ba ngôi ? :, typeof và nullish coalescing ??. Chúng là những khối xây dựng cho biểu thức và luồng điều khiển.
== và === trong JavaScript khác nhau thế nào?
=== kiểm tra cả giá trị lẫn kiểu dữ liệu đều khớp. == ép kiểu trước khi so sánh, nên 0 == "0" là true nhưng 0 === "0" là false. Hãy mặc định dùng ===: các quy tắc ép kiểu của == đủ tinh vi để gây ra những bug bạn khó phát hiện khi review code.
Toán tử ba ngôi trong JavaScript làm gì?
condition ? a : b là một câu lệnh if/else viết trên một dòng và trả về một giá trị. Nếu condition là truthy, kết quả là a, ngược lại là b. Nó hữu ích cho các biểu thức điều kiện ngắn như const label = count === 1 ? 'item' : 'items', nhưng lồng nhiều toán tử ba ngôi sẽ nhanh chóng khó đọc.
Khi nào nên dùng ?? thay cho ||?
|| dùng giá trị dự phòng với mọi giá trị falsy, kể cả 0, "" và false. ?? chỉ dùng giá trị dự phòng với null hoặc undefined. Nếu bạn muốn count ?? 10 giữ lại một số 0 hợp lệ, hãy dùng ??. Nếu bạn thực sự muốn mọi giá trị falsy đều dẫn tới giá trị dự phòng, hãy dùng ||.