Độ ưu tiên của toán tử (Operator Precedence) trong JavaScript

Javascript căn bản | by Học Javascript

Khi viết code trong JavaScript, chúng ta thường sử dụng nhiều toán tử khác nhau như toán tử số học, so sánh, logic, gán, và điều kiện. Tuy nhiên, khi một biểu thức chứa nhiều toán tử cùng lúc, trình biên dịch JavaScript sẽ thực hiện chúng theo một thứ tự nhất định. Thứ tự này được quyết định bởi độ ưu tiên của toán tử (Operator Precedence).

Hiểu rõ về độ ưu tiên và quy tắc kết hợp của các toán tử giúp lập trình viên tránh được những lỗi không mong muốn, đồng thời viết code ngắn gọn, tối ưu hơn. Trong bài viết này, chúng ta sẽ tìm hiểu cách JavaScript xử lý các toán tử với độ ưu tiên khác nhau, từ đó áp dụng chúng một cách hiệu quả trong lập trình.

Độ ưu tiên của toán tử trong JavaScript

Trong JavaScript, độ ưu tiên của toán tử (Operator Precedence) xác định thứ tự thực hiện của các toán tử trong một biểu thức có nhiều toán tử. Nếu một biểu thức chứa nhiều toán tử khác nhau, JavaScript sẽ thực hiện chúng theo độ ưu tiên đã được quy định sẵn, thay vì từ trái sang phải một cách ngẫu nhiên.

Ví dụ, trong biểu thức:

let result = 10 + 5 * 2;
console.log(result); // Output: 20

Ở đây, phép nhân (*) có độ ưu tiên cao hơn phép cộng (+), nên 5 * 2 được tính trước, sau đó mới cộng với 10, dẫn đến kết quả là 20.

Tại sao cần hiểu về độ ưu tiên của toán tử?

Việc hiểu rõ độ ưu tiên của toán tử giúp lập trình viên:

  • Tránh lỗi logic: Nếu không hiểu đúng thứ tự thực hiện của các toán tử, có thể dẫn đến kết quả sai.
  • Viết code ngắn gọn và hiệu quả hơn: Không cần thêm dấu ngoặc đơn không cần thiết nếu đã hiểu rõ độ ưu tiên.
  • Dễ dàng đọc và bảo trì code: Khi viết code rõ ràng, người khác cũng dễ hiểu và sửa đổi hơn.

Ví dụ, nếu không hiểu rõ độ ưu tiên, có thể viết như sau:

let result = (10 + 5) * 2; // Thêm dấu ngoặc không cần thiết
Nhưng thực tế, vì phép nhân có độ ưu tiên cao hơn phép cộng, nên có thể viết đơn giản:
let result = 10 + 5 * 2; // Kết quả vẫn đúng mà không cần dấu ngoặc

Ví dụ minh họa về cách toán tử có độ ưu tiên khác nhau ảnh hưởng đến kết quả

Dưới đây là một số ví dụ về độ ưu tiên của toán tử:

Ví dụ 1: Toán tử số học có độ ưu tiên khác nhau

let x = 10 + 5 * 2;  
console.log(x); // Output: 20
  • Phép nhân (*) có độ ưu tiên cao hơn phép cộng (+), nên 5 * 2 được thực hiện trước, rồi mới cộng với 10.

Ví dụ 2: Toán tử so sánh và logic có độ ưu tiên khác nhau

let a = 5 > 3 && 8 < 10;
console.log(a); // Output: true
  • Toán tử so sánh (>, <) có độ ưu tiên cao hơn toán tử logic (&&), nên 5 > 38 < 10 được tính trước, sau đó kết quả true && true trả về true.

Ví dụ 3: Toán tử gán có độ ưu tiên thấp

let b = 10;
let c = 5 + (b = 20);
console.log(c); // Output: 25
  • Toán tử gán (=) có độ ưu tiên thấp hơn phép cộng (+), nên b = 20 được thực hiện trước, sau đó 5 + 20 mới được tính, kết quả là 25.

Ví dụ 4: Sử dụng dấu ngoặc để thay đổi độ ưu tiên

Nếu muốn thay đổi thứ tự thực hiện của các toán tử, có thể dùng dấu ngoặc ():

let y = (10 + 5) * 2;
console.log(y); // Output: 30

Lần này, phép cộng 10 + 5 được thực hiện trước do có dấu ngoặc, rồi mới nhân 2, kết quả là 30.

Bảng độ ưu tiên của toán tử trong JavaScript

Trong JavaScript, các toán tử có độ ưu tiên khác nhau, ảnh hưởng đến thứ tự thực hiện trong một biểu thức. Để hiểu rõ cách các toán tử hoạt động, cần biết về:

  • Độ ưu tiên cao (Higher precedence) và độ ưu tiên thấp (Lower precedence).
  • Bảng xếp hạng độ ưu tiên từ cao đến thấp.
  • Quy tắc kết hợp (Associativity) của toán tử.

Độ ưu tiên cao (Higher precedence) và độ ưu tiên thấp (Lower precedence)

  • Toán tử có độ ưu tiên cao sẽ được thực hiện trước trong biểu thức.
  • Toán tử có độ ưu tiên thấp sẽ được thực hiện sau, trừ khi bị thay đổi bằng dấu ngoặc () để ép buộc thứ tự thực hiện.

Ví dụ về độ ưu tiên cao hơn:

let result = 10 + 5 * 2;
console.log(result); // Output: 20
  • Phép nhân (*) có độ ưu tiên cao hơn phép cộng (+), nên 5 * 2 được thực hiện trước, rồi mới cộng với 10.

Ví dụ về độ ưu tiên thấp hơn:

let result = (10 + 5) * 2;
console.log(result); // Output: 30
  • Dấu ngoặc () có độ ưu tiên cao nhất, nên 10 + 5 được thực hiện trước, rồi mới nhân 2.

Bảng xếp hạng độ ưu tiên của toán tử trong JavaScript

Dưới đây là bảng xếp hạng các toán tử từ cao nhất đến thấp nhất trong JavaScript:

Mức Toán tử Mô tả Quy tắc kết hợp
20 () Nhóm biểu thức (dấu ngoặc) -
19 . [] ?. Truy cập thuộc tính Trái → Phải
18 new (không có đối số) Tạo instance -
17 () Gọi hàm Trái → Phải
16 new (có đối số) Tạo instance với tham số Phải → Trái
15 ++ -- (hậu tố) Tăng/Giảm giá trị sau khi trả về Trái → Phải
14 ++ -- (tiền tố) + - ~ ! typeof void delete Toán tử một ngôi Phải → Trái
13 ** Lũy thừa Phải → Trái
12 * / % Nhân, chia, chia lấy dư Trái → Phải
11 + - Cộng, trừ Trái → Phải
10 << >> >>> Dịch bit Trái → Phải
9 < <= > >= instanceof in So sánh Trái → Phải
8 == != === !== So sánh bằng Trái → Phải
7 & AND bitwise Trái → Phải
6 ^ XOR bitwise Trái → Phải
5 ` ` OR bitwise
4 && AND logic Trái → Phải
3 ` `
2 ?? Nullish Coalescing Trái → Phải
1 ? : Toán tử ba ngôi Phải → Trái
0 = += -= *= /= %= **= &= ` = ^= <<= >>= >>>=` Toán tử gán
-1 , Phân tách biểu thức Trái → Phải

Quy tắc kết hợp (Associativity) của toán tử

Quy tắc kết hợp (Associativity) xác định hướng thực hiện của các toán tử có cùng độ ưu tiên. Có hai loại:

Trái Phải (Left to Right): Toán tử thực hiện từ trái sang phải.

  • Ví dụ: Phép nhân (*), phép cộng (+), so sánh (<, >, ==, etc.).

Ví dụ minh họa:

console.log(10 - 2 - 3); // Output: 5

Thực hiện từ trái sang phải: (10 - 2) = 8, rồi 8 - 3 = 5.

Phải Trái (Right to Left): Toán tử thực hiện từ phải sang trái.

  • Ví dụ: Toán tử gán (=), toán tử ba ngôi (?:), lũy thừa (**).

Ví dụ minh họa:

let x = y = 10;
console.log(x, y); // Output: 10 10

y = 10 thực hiện trước, sau đó x = y, nên cả hai đều là 10.

Ví dụ với toán tử lũy thừa:

console.log(2 ** 3 ** 2); // Output: 512

3 ** 2 được thực hiện trước (9), rồi 2 ** 9 = 512.

Các nhóm toán tử và độ ưu tiên trong JavaScript

Toán tử trong JavaScript được chia thành nhiều nhóm khác nhau, mỗi nhóm có độ ưu tiên riêng. Hiểu rõ độ ưu tiên giúp lập trình viên kiểm soát cách biểu thức được tính toán và tránh lỗi logic trong chương trình.

Toán tử dấu ngoặc đơn (Grouping - ())

Dấu ngoặc đơn ()độ ưu tiên cao nhất, được dùng để nhóm biểu thức và thay đổi thứ tự thực hiện mặc định của các toán tử.

Ví dụ minh họa:

let result1 = 10 + 5 * 2;  
console.log(result1); // Output: 20 (Nhân được thực hiện trước)  

let result2 = (10 + 5) * 2;  
console.log(result2); // Output: 30 (Cộng được thực hiện trước)  
  • Trong result1, phép nhân 5 * 2 được thực hiện trước do có độ ưu tiên cao hơn phép cộng.
  • Trong result2, dấu ngoặc () ép 10 + 5 được tính trước.

Toán tử truy cập và gọi hàm (., [], ())

Nhóm này bao gồm:

  • Truy cập thuộc tính: object.property
  • Truy xuất phần tử mảng: array[index]
  • Gọi hàm: function()

Ví dụ minh họa:

let obj = { name: "Alice", age: 25 };
console.log(obj.name); // Output: Alice (Truy cập thuộc tính)

let arr = [10, 20, 30];
console.log(arr[1]); // Output: 20 (Truy xuất mảng)

function greet() {
    return "Hello, world!";
}
console.log(greet()); // Output: Hello, world! (Gọi hàm)

Toán tử số học (Arithmetic Operators)

Nhóm này bao gồm:

  • Nhân (*), chia (/), chia lấy dư (%) → có độ ưu tiên cao hơn.
  • Cộng (+), trừ (-) → có độ ưu tiên thấp hơn.

Ví dụ minh họa:

let result = 10 + 5 * 2 - 3;  
console.log(result); // Output: 17  
  • Phép nhân 5 * 2 được thực hiện trước (10), sau đó mới cộng 10 và trừ 3.

Nếu muốn thay đổi thứ tự thực hiện, có thể sử dụng dấu ngoặc:

let result2 = (10 + 5) * 2 - 3;  
console.log(result2); // Output: 27  
  • Dấu ngoặc () ép 10 + 5 được tính trước, rồi nhân 2, sau đó trừ 3.

Toán tử so sánh (Comparison Operators)

Nhóm này bao gồm:

  • Lớn hơn, nhỏ hơn: >, <, >=, <=
  • So sánh bằng: ==, ===, !=, !==

Toán tử so sánh có độ ưu tiên thấp hơn toán tử số học, nhưng cao hơn toán tử logic.

Ví dụ minh họa:

console.log(10 + 5 > 12 - 2); // Output: true  
  • Phép cộng 10 + 5 (15) và trừ 12 - 2 (10) được thực hiện trước do có độ ưu tiên cao hơn.
  • Sau đó, 15 > 10 được đánh giá thành true.

Toán tử logic (Logical Operators)

Nhóm này gồm:

  • NOT (!) → có độ ưu tiên cao nhất.
  • AND (&&) → có độ ưu tiên cao hơn OR.
  • OR (||) → có độ ưu tiên thấp nhất.

Ví dụ minh họa:

let a = true, b = false, c = true;
console.log(!a || b && c); // Output: false  
  • && có độ ưu tiên cao hơn ||, nên b && c (false && true) được thực hiện trước → false.
  • !a (!true) thành false.
  • Cuối cùng, false || false trả về false.

Muốn đổi thứ tự, có thể dùng dấu ngoặc:

console.log(!(a || b) && c); // Output: false  

Toán tử gán (Assignment Operators)

Nhóm này gồm các toán tử gán:

  • = += -= *= /= %= **=

Toán tử gán có độ ưu tiên rất thấpquy tắc kết hợp từ phải sang trái.

Ví dụ minh họa:

let x = y = 10 + 5;  
console.log(x, y); // Output: 15 15  
  • 10 + 5 được tính trước, sau đó y = 15, rồi x = y.

Toán tử điều kiện (Ternary Operator ? :)

Toán tử ba ngôi có độ ưu tiên cao hơn toán tử gán nhưng thấp hơn toán tử so sánh.

Ví dụ minh họa:

let age = 20;
let message = age >= 18 ? "Adult" : "Minor";
console.log(message); // Output: Adult  
  • age >= 18 được tính trước, sau đó trả về "Adult" hoặc "Minor".

Toán tử bitwise (Bitwise Operators)

Nhóm này gồm:

  • AND bitwise (&), OR bitwise (|), XOR bitwise (^)
  • Dịch bit (<<, >>, >>>)

Toán tử bitwise có độ ưu tiên nằm giữa toán tử so sánh và toán tử logic.

Ví dụ minh họa:

console.log(5 & 1); // Output: 1  
console.log(5 | 1); // Output: 5  
console.log(5 ^ 1); // Output: 4  
console.log(5 << 1); // Output: 10  
  • 5 trong hệ nhị phân là 101, 1001.
  • 5 & 1: Cả hai bit phải là 10011.
  • 5 | 1: Ít nhất một bit là 11015.
  • 5 ^ 1: Khác nhau thì 1, giống nhau thì 01004.
  • 5 << 1: Dịch trái 101 một lần → 101010.

Cách sử dụng độ ưu tiên toán tử hiệu quả trong JavaScript

Hiểu rõ độ ưu tiên của toán tử giúp lập trình viên viết mã dễ đọc, tránh lỗi logic và cải thiện hiệu suất chương trình. Dưới đây là một số cách sử dụng hiệu quả độ ưu tiên toán tử trong JavaScript.

Sử dụng dấu ngoặc đơn để tăng tính rõ ràng

Tại sao nên dùng dấu ngoặc đơn?

  • Khi có nhiều toán tử trong một biểu thức, dấu ngoặc giúp làm rõ thứ tự thực hiện, tránh sai sót do nhầm lẫn độ ưu tiên.
  • Giúp người đọc dễ hiểu hơn, ngay cả khi họ không nhớ chính xác độ ưu tiên của từng toán tử.

Ví dụ minh họa:
Không dùng dấu ngoặc:

let result = 10 + 5 * 2 - 3;
console.log(result); // Output: 17  

Ở đây, phép nhân (5 * 2 = 10) được thực hiện trước, sau đó cộng (10 + 10 = 20), rồi trừ (20 - 3 = 17).

Dùng dấu ngoặc để làm rõ thứ tự:

let result2 = (10 + 5) * (2 - 3);
console.log(result2); // Output: -15  
  • Dấu ngoặc ép 10 + 5 = 152 - 3 = -1 được tính trước, sau đó mới nhân lại.

Quy tắc chung: Nếu có thể, hãy sử dụng dấu ngoặc để làm rõ ý định của bạn, ngay cả khi nó không bắt buộc.

Tránh nhầm lẫn giữa các toán tử có độ ưu tiên gần nhau

Vấn đề: Một số toán tử có độ ưu tiên gần nhau, nếu không cẩn thận có thể dẫn đến lỗi logic.

Ví dụ minh họa:

let isValid = true && false || true;
console.log(isValid); // Output: true  
  • Do && có độ ưu tiên cao hơn ||, nên true && false được thực hiện trước → false.
  • Sau đó, false || truetrue.

Nếu muốn true && (false || true), cần dùng dấu ngoặc:

let isValid2 = true && (false || true);
console.log(isValid2); // Output: true  

Cách tránh nhầm lẫn: Khi kết hợp nhiều toán tử logic (&&, ||), nên dùng dấu ngoặc để làm rõ ý định.

Kiểm tra quy tắc kết hợp của toán tử trước khi viết biểu thức phức tạp

Quy tắc kết hợp (Associativity) xác định hướng thực hiện phép toán khi các toán tử có cùng độ ưu tiên.

Loại toán tử Quy tắc kết hợp
Toán tử số học (+, -, *, /, %) Trái sang phải
Toán tử so sánh (>, <, >=, <=) Trái sang phải
Toán tử logic (&&, `
Toán tử gán (=, +=, -=, *=, /=, **=) Phải sang trái
Toán tử ba ngôi (? :) Phải sang trái

Ví dụ minh họa:
Toán tử gán (=) có kết hợp từ phải sang trái, nên đoạn mã sau:

let a, b, c;
a = b = c = 10;
console.log(a, b, c); // Output: 10 10 10  
  • c = 10 được thực hiện trước, sau đó b = c, rồi a = b.

Cách tránh lỗi: Khi dùng các toán tử có kết hợp từ phải sang trái như =, ? :, hãy kiểm tra lại logic của biểu thức.

Ví dụ thực tế về độ ưu tiên toán tử

Ví dụ 1: Viết điều kiện kiểm tra độ tuổi hợp lệ

let age = 20;
if (age >= 18 && age <= 30) {
    console.log("Bạn đủ điều kiện!");
} else {
    console.log("Bạn không đủ điều kiện!");
}
// Output: Bạn đủ điều kiện!

>=<= được thực hiện trước &&, giúp kiểm tra phạm vi 18-30 chính xác.

Ví dụ 2: Kiểm tra đăng nhập với toán tử ba ngôi

let isLoggedIn = false;
let message = isLoggedIn ? "Welcome back!" : "Please log in.";
console.log(message); // Output: Please log in.
  • Toán tử ? : có độ ưu tiên cao hơn =, nên biểu thức isLoggedIn ? "Welcome back!" : "Please log in." được tính trước khi gán vào message.

Ví dụ 3: Kiểm tra chuỗi hợp lệ và không chứa khoảng trắng thừa

let input = "  Hello World!  ";
let cleanedInput = input.trim().length > 0 ? input.trim() : "Invalid input";
console.log(cleanedInput); // Output: "Hello World!"
  • trim() được gọi trước do dấu . có độ ưu tiên cao hơn ? :.

Kết bài

Độ ưu tiên của toán tử là một khía cạnh quan trọng trong JavaScript, giúp xác định thứ tự thực hiện các phép toán trong một biểu thức. Hiểu rõ về độ ưu tiên giúp lập trình viên viết mã chính xác, tránh lỗi logic và cải thiện hiệu suất của chương trình.

Để sử dụng toán tử hiệu quả, bạn nên:

  • Sử dụng dấu ngoặc đơn () để làm rõ thứ tự thực hiện.
  • Tránh nhầm lẫn giữa các toán tử có độ ưu tiên gần nhau.
  • Kiểm tra quy tắc kết hợp (trái sang phải hoặc phải sang trái) khi viết biểu thức phức tạp.
  • Thực hành nhiều ví dụ để nắm vững cách hoạt động của các toán tử trong JavaScript.

Bằng cách áp dụng các nguyên tắc trên, bạn sẽ có thể viết mã dễ đọc, dễ hiểu và ít lỗi hơn, giúp nâng cao kỹ năng lập trình JavaScript của mình.

Bài viết liên quan