Hướng dẫn dùng conditionals JavaScript

Một trong những công cụ mạnh mẽ và được sử dụng nhiều nhất của các ngôn ngữ lập trình chính là các câu lệnh điều kiện, tạo ra các nhánh code chỉ được chạy khi đáp ứng một số điều kiện nhất định. Javascript có 3 loại điều kiện chính: khối if/else, switch statements và biểu thức điều kiện. 3 câu lệnh này có thể giúp bạn tạo ra được những dòng code rất awsome!

Giống như những câu lệnh khác, một câu lệnh điều kiện không được tối ưu sẽ rất dễ gây ra lỗi và khó tối ưu sau này. Vậy nên dưới đây, mình xin phép được giới thiệu một số cách viết áp dụng vài nguyên tắc cơ bản để giúp chúng ta tối ưu được những câu lệnh điều kiện này một cách tốt nhất.

Khái niệm cơ bản nhất của Javascript Conditionals

Như mình đã nói trước đó, Javascript ( JS ) có 3 loại điều kiện chính. Đầu tiên chính là câu lệnh if/else. Định nghĩa về câu lệnh này rấy đơn giản: Nếu điều kiện đúng , đoạn code trong đó sẽ được chạy. Ví dụ:

if (condition){
   // code sẽ được chạy nếu như điều kiện là đúng
}

Ta cũng có thể chạy thêm đoạn mã khác dựa trên cùng một điều kiện, bằng cách thêm từ khoá else, như ví dụ dưới đây:

if (condition){
   // code sẽ được chạy nếu như điều kiện là đúng
} else {
   // nếu không đáp ứng điều kiện trên nó sẽ chạy xuống đâyyy
}

Bạn cũng có thể thêm nhiều điều kiện bằng cách sử dụng các câu lệnh else/if như dưới đây:

if (condition){
   // code sẽ được chạy nếu như thoả mãn condition
} else if(condition2) {
   // nếu không đáp ứng điều kiện trên nó sẽ chạy xuống đâyyy
   // và kèm thêm thoả mãn condition2
} else {
   // Nếu không vô 2 cái kia thì sẽ chạy vào đây
}

Câu lệnh switch, nó kiểm tra giá trị của 1 hoặc nhiều case đối với expression. Nếu không có case nào thoả mãn, thì bạn có thể cung cấp một case mặc định - được gọi là default:

switch(expression){
   case value1:
     // code được thực thi nếu khớp với value1
     break; 
   case value2:
     // code được thực thi nếu khớp với value2
     break;
   default:
     // code code được thực thi nếu ko thoả mãn những case trên
     break;
}

Một trong những lưu ý quan trọng nhất của switch statements chính là break or return. Chúng sẽ giúp ngắt việc thực thi case không thoả mãn và tiếp tục chạy xuống những case phía dưới. Ví dụ:

const name = "Jim";
switch(name){
   case "Mark":
     // This code will not run
   case "Jim":
     // This code and all other code after will run
   case "Sarah":
     ...
   default:
     ...
}

Kiểu điều kiện cuối cùng chính là biểu thức điều kiện. Chúng cũng là một cách hay được sử dụng để đơn giản hoá mệnh đề if/else.

const myValue = condition ? valueIfTrue : valueIfFalse;

Best Practices cho Conditionals

Nếu như không được tối ưu và sử dụng đúng cách, các câu lệnh điều kiện sẽ là một cản trở khó chịu trong quá trình xây dựng sản phẩm của bạn. Để giảm trừ điều này, có một vài nguyên tắc mà bạn nên tuân theo để tránh cho những câu lệnh điều kiện mà bạn viết ra trở thành một mớ hỗn độn.

  1. Đừng sử dụng câu lệnh If để gán giá trị có điều kiện. Ví dụ:
   let myVal
   if (condition) {
      myVal = valueIfTrue
   } else {
      myVal = valueIfFalse
   }

Với bài toán trên, bạn nên xử lý như thế này:

const myVal = condition ? valueIfTrue : valueIfFalse
  1. Tránh lồng quá nhiều if/else. Về bản chất, else/if là một khối if lồng nhau. Ta thường viết nó như thế này:
let result
if (condition) {
   // calculate result_v1
} else if (condition2) {
   // calculate result_v2
} else {
   // default calculation
}
return result

Thực tế, nó chính là như thế này:

let result
if (condition){
   // calculate result_v1
} else {
   if(condition2) {
      // calculate result_v2
   } else {
      // default calculation
   }
}
return result

Với đồng bùng nhùng bên trên, ta có thể viết lại chúng như sau:

if (condition) {
   // return calculated result_v1
}

if (condition2) {
   // return calculated result_v2
}

// return default calculation

Một cách khác để tránh những If blocks lồng nhau, đó chính là sử dụng switch statements, như dưới đây:

switch (expresion){
   case one:
     // return calculated result_v1
   case two:
     // return calculated result_v2
   default:
     // return default calculation
 }

Trên đây là một số chia sẻ về câu lệnh điều kiện trong JS cũng như một số nguyên tắc nhỏ trong việc viết chung. Bài viết còn nhiều thiếu sót, mong sẽ nhận được ý kiến đóng góp từ mọi người.

Tham khảo: How to Make Your Conditional Statements Easier to Read in JavaScript

Biểu thức điều kiện là một trong những khía cạnh rất quan trong trong mọi ngôn ngữ lập trình. Chúng ta đã quen với các mệnh đề điều kiện như if..elif..else hay switch. Chúng là những cú pháp hữu dụng để tạo ra những quyết định trong lập trình.

Bài viết này sẽ tập trung vào các biểu thức điều kiện trong Javascript và cách sử dụng chúng một cách ngắn gọn so với các mệnh đề điều kiện.

Biểu thức và Mệnh đề (Expressions vs Statements)

Trước khi đi vào nội dung chính, chúng ta cần phân biệt giữa biểu thức và mệnh đề trong Javascript. Nếu coi Javascript là một ngôn ngữ có ngữ pháp thì biểu thức chính là cụm từ, trong khi đó mệnh đề là một câu hoàn chỉnh.

Biểu thức có thể là bất kỳ thể hiện nào mà Javascript engine có thể tính toán và trả về một giá trị. Chẳng hạn như: thể hiện của biến, phép gán, biểu thức hàm, phép logic, toán tử bitwise, phép truy cập thuộc tính của đối tượng, lời gọi hàm, eval, ....

Đoạn code dưới đây chỉ ra một số biểu thức Javascript:

// number literal
0xFF

// array literal
[]

// object literal
{}

// regexp literal
/^\d+$/

// logical AND operation
(x && y)

// bitwise XOR operation
(x ^ y)

// ternary operation
(x ? y : z)

// arithmetic operation
(x + y) / z

// assignment
x = 'string'

// function expression
(function x(y) {})

// function invocation
x(100)

// object property access
obj.students[0].name

Mệnh đề là bất kỳ thể hiện hay câu lệnh nào mà Javascript engine có thể thực thi để chạy chương trình hoặc gây ra những tác động kèm theo khác. Ví dụ: mệnh đề điều kiện, khai báo biến hoặc hàm, vòng lặp, throw, return, try/catch/finally, ...

Một số biểu thức Javascript như phép gán và lời gọi hàm có thể gây ra những tác động kèm theo. Do đó, chúng lại được coi là một mệnh đề (mệnh đề biểu thức).

Phép điều kiện và giá trị kiểu boolean

Trong Javascript, phép điều kiện có thể là bất kỳ biểu thức hợp lệ nào. Thông thường, biểu thức điều kiện sẽ được tính toán để trả về một trong hai giá trị kiểu booleans: true hoặc false

Việc hiểu đúng cách mà Javascript engine chuyển đổi biểu thức điều kiện về giá trị boolean là điều cần thiết để viết các phép logic điều kiện một cách chính xác và khả dự.

Hai khái niệm cơ bản có thể hiểu về phép chuyển đổi này:

  • Xác định giá trị truthy and falsy
  • Hiểu về phép logic short-circuiting

Truthy vs Falsy

Mọi giá trị trong Javascript đều có thể phân loại thành truthy hay falsy. Những giá trị sau được coi là falsy trong Javascript.

  • '' hoặc "" hoặc `` (chuỗi rỗng)
  • 0 hoặc -0 (số 0)
  • null
  • undefined
  • NaN
  • false

Những giá trị khác các giá trị trên sẽ được coi là truthy. Các giá trị truthy sẽ được ép kiểu ngầm định thành giá trị true. Trong khi giá trị falsy sẽ trả về giá trị false.

Tuy nhiên, việc chuyển đổi này có thể được khai báo tường minh nhờ hàm Boolean.

function toBoolean(value) {
  return Boolean(value);
}

Ngoài ra, toán tử logic NOT (!) cũng chuyển đổi một giá trị về kiểu boolean. Toán tử ! chuyển đổi toán hạng của nó thành giá trị phủ định ở kiểu boolean. Do đó, giá trị này luôn là giá trị boolean.

Sử dụng toán tử ! sẽ trả về false trên những giá trị truthy và true trên những giá trị falsy. Để chuyển về giá trị boolean tương ứng, chúng ta cần sử dụng ! hai lần.

function toBoolean(value) {
  return !!value;
}

Short-Circuiting (Mạch chập)

Toán tử AND (&&) và OR (||) đều yêu cầu hai toán hạng và được sử dụng để thực hiện phép toán Boolean trên những toán hạng này.

Cho hai toán hạng kiểu boolean (true hoặc false)

  • && chỉ trả về true nếu cả hai toán hạng có giá trị kiểu boolean là true, nếu không nó sẽ trả về fasle.
  • || chỉ trả về false nếu cả hai toán hạng có giá trị kiểu boolean là false, nếu không nó sẽ trả về true.

Chú ý rằng toán tử && có độ ưu tiên hơn ||. Do đó, nó sẽ được tính toán trước. Khi sử dụng cả hai toán tử này trong cùng một biểu thức, có thể sử dụng dấu ngoặc () để nhóm các phép tính theo thứ tự ưu tiên.

false && true || true; // true
false && (true || true); // false

Khi sử dụng những toán tử này, toán hạng đầu tiên luôn được tính toán trước. Tuy nhiên, toán hạng thứ hai có thể không bao giờ được sử dụng tùy theo kết quả tính toán của toán hạng đầu tiên. Điều này được gọi là short-circuiting (mạnh chập hay đoản mạch).

Toán tử &&|| không phải lúc nào cũng trả về giá trị kiểu boolean. Thông thường, chúng có thể trả về bất kỳ giá trị nào. Dưới đây là mô tả chính xác về tính đoản mạch của chúng:

  • Toán tử && sẽ kiểm tra toán hạng đầu tiên. Nếu kết quả là truthy thì toán hạng sẽ được tính toán và kết quả trả về là giá trị của toán hạng thứ hai. Tuy nhiên nếu giá trị của toán hạng đầu tiên là falsy thì toán hạng thứ hai không bao giờ được tính toán, kết quả trả về là giá trị falsy của toán hạng đầu.
(a && b) === a; // `a` is falsy
(a && b) === b; // `a` is truthy
  • Toán tử || cũng sẽ kiểm tra toán hạng đầu tiên. Nếu kết quả là truthy thì toán hạng thứ hai sẽ không bao giờ được tính toán, kết quả trả về là giá trị truthy từ toán hạng đầu tiên. Tuy nhiên, nếu giá trị của toán hạng đầu là falsy thì kết quả trả về là giá trị của toán hạng thứ hai.
(a || b) === a; // `a` is truthy
(a || b) === b; // `a` is falsy

Thay thế mệnh đề bằng biểu thức

1. Đơn giản hóa mệnh đề If

Rất nhiều câu điện kiện if có thể dễ dàng được thay thế bởi biểu thức điều kiện bằng cách áp dụng khái niệm mạch chập. Xem xét ví dụ sau:

if (user && user.canDeletePost) {
  deletePost();
}

Trong đoạn code này, mệnh đề if đảm bảo rằng hàm deletePost() chỉ được gọi khi phép điều kiện trả về true

Mệnh đề if trên có thể được thay thế bằng biểu thức điều kiện rất đơn giản như sau:

user && user.canDeletePost && deletePost();

Mặc dù biểu thức điều kiện này có cách thực thi giống với mệnh đề điều kiện trên, nhưng thực chất chúng khác nhau.

Mệnh đề điều kiện trả về một giá trị. Do đó, nó có thể được gán cho một biến hoặc được sử dụng ở nơi khác mà yêu cầu một giá trị cụ thể.

Việc sử dụng biểu thức điều kiện như này đồng nghĩa với việc phải rất cận trọng về khái niệm mạch chập. Rất có thể toán hạng không được thực thi như đã đề cập ở mục trước của bài viết về mạch chập.

2. Mệnh đề If...Else

Xem xét ví dụ đơn giản sau để xác định độ mạnh của mật khẩu:

let strength = null;

if (password.length > 7) {
  strength = 'Strong';
} else {
  strength = 'Weak';
}

Ý tưởng của đoạn code trên rất đơn giản: Kiểm tra nếu mật khẩu có độ dài lớn hơn 7 ký tự thì gán trị cho biến là "Strong" , ngược lại gán à "Weak"

Đoạn code trước có thể được rút gọn như sau:

const strength = (password.length > 7) && 'Strong' || 'Weak';

Đoạn code này thực hiện giống hệt như đoạn code lúc trước, tất cả chỉ trong một dòng. Điều này trông khá ổn. Đoạn code dưới đây sẽ giải thích cơ chế tính toán của biểu thức điều kiện ở đoạn code trên.

let password = 'long_password';

console.log(password.length > 7); // true
console.log(password.length > 7 && 'Strong'); // "Strong"
console.log(password.length > 7 && 'Strong' || 'Weak'); // "Strong"

password = 'short';

console.log(password.length > 7); // false
console.log(password.length > 7 && 'Strong'); // false
console.log(password.length > 7 && 'Strong' || 'Weak'); // "Weak"

Có một cách khác để viết lại những biểu thức điện kiện if...else là sử dụng toán tử điều kiện, hay còn được gọi là toán tử ba ngôi (ternary operator). Cú pháp như sau:

// If condition is truthy, evaluate and return A,
// otherwise evaluate and return B

condition ? A : B

Đoạn code kiểm tra mật khẩu lúc trước có thể được viết lại sử dụng toán tử ba ngôi như sau:

const strength = (password.length > 7) ? 'Strong' : 'Weak';

Mặc dùng trong ví dụ này, toán tử ba ngôi và toán tử logic hoạt động giống nhau nhưng cần nhớ rằng chúng không thể thay thế được cho nhau.

Tốt hơn hết là nên sử dụng toán tử ba ngôi trong những trường hợp không biết rõ về toán hạng sẽ được thực hiện.

Xem xét đoạn code sau để hiểu về sự nguy hiểm của việc sử dụng toán tử logic cho những trường hợp như này:

// LOGICAL OPERATORS
// If condition is truthy and A is truthy, return A,
// otherwise evaluate and return B

// Danger: A will never be returned if it is falsy

condition && A || B


// TERNARY OPERATOR
// If condition is truthy, evaluate and return A,
// otherwise evaluate and return B

condition ? A : B

Dưới đây là một mệnh đề rất quen thuộc thường được tìm thấy ở các thư viện AJAX đa nền tảng:

let xmlhttp = null;

if (window.XMLHttpRequest) {
  
  // Modern browsers
  xmlhttp = new XMLHttpRequest();
  
} else if (window.ActiveXObject) {
  
  // Older versions of Internet Explorer (IE <= 6)
  xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
  
}

Sử dụng toán tử logic:

const xmlhttp = window.XMLHttpRequest && new XMLHttpRequest()
  || window.ActiveXObject && new ActiveXObject('Microsoft.XMLHTTP')
  || null;

Sử dụng toán tử ba ngôi:

const xmlhttp = window.XMLHttpRequest
  ? new XMLHttpRequest()
  : window.ActiveXObject
    ? new ActiveXObject('Microsoft.XMLHTTP')
    : null;

Mẹo nhỏ và cú pháp viết tắt.

Dưới đây là một số mẹo và cú pháp viết tắt hữu dụng khi sử dụng các toán tử logic và toán tử điều kiện:

Chuẩn hóa về kiểu Boolean

Cho giá trị value cần chuẩn hóa để luôn trả về giá trị kiểu boolean theo quy tắc sau:

  • Nếu value là giá trị boolean thì trả về giá trị value
  • Nếu value không phải giá trị boolean , giá trị boolean mặc định sẽ được trả về (true hoặc fasle)

Đoạn code dưới đây mô tả phép chuẩn hóa trên:

// boolOrFalse()
// Return value if it is a boolean,
// otherwise return false

const boolOrFalse = value => {
  return (typeof value === 'boolean') && value;
}

console.log(boolOrFalse()); // false
console.log(boolOrFalse(0)); // false
console.log(boolOrFalse('')); // false
console.log(boolOrFalse(false)); // false
console.log(boolOrFalse(true)); // true


// boolOrTrue()
// Return value if it is a boolean,
// otherwise return true

const boolOrTrue = value => {
  return (typeof value !== 'boolean') || value;
}

console.log(boolOrTrue()); // true
console.log(boolOrTrue(0)); // true
console.log(boolOrTrue('')); // true
console.log(boolOrTrue(false)); // false
console.log(boolOrTrue(true)); // true

Định lý De Morgan

Định lý toán học quen thuộc này có thể được mô tả như sau:

// These two are equivalent
!A && !B == !(A || B)

// Also these two
!A || !B == !(A && B)

Sự đồng nhất của Boolean

Khi xử lý với giá trị boolean, có một số phép đồng nhất luôn đúng. Cho A, BC là những giá trị boolean, đoạn code sau sẽ chỉ ra sự đồng nhất này:

// NOT Conversion
!!A == A
!!B == B
!!C == C

// AND to OR Conversion
A && B == !(!A || !B)

// OR to AND Conversion
A || B == !(!A && !B)

// Removing nested AND
A || (B && C) == A || B && C

// Removing nested OR
A && (B || C) == !(!A || !B && !C)

Những toán tử ba ngôi phức hợp

Như đã đề cập ở các phần trước của bài viết, toán tử ba ngôi có thể được lồng với nhau để xử lý những đoạn logic liên quan đến mệnh đề if...else

Tuy nhiên, để có thể sử dụng chúng hiệu quả trong những biểu thức phức hợp thì cần hiểu về độ ưu tiên và tính kết hợp của toán tử ba ngôi.

  • Toán tử ba ngôi có độ ưu tiên thấp nhất so với các toán tử khác. Do đó nó được tính toán cuối cùng khi được sử dụng chung với các toán tử khác có độ ưu tiên cao hơn.
/ this expression
A ? B + C && D : E || F && G

// will be evaluated as
A ? ((B + C) && D) : (E || (F && G))
  • Toán tử ba ngôi có tính kết hợp từ phải qua trái. Do đó, đối với các toán tử ba ngôi được sử dụng trong cùng một biểu thức, chúng được phân tích từ phải sang trái.
// this expression
A ? B : C ? D : E ? F : G

// will be evaluated as
(A ? B : (C ? D : (E ? F : G)))

Khi sử dụng nhiều toán tử ba ngôi trong cùng một biểu thức, dấu ngoặc () có thể cần được sử dụng để thay đổi thứ tự tính toán. Ví dụ:

// this expression
A ? B : (C ? D : E) ? F : G

// will be evaluated as
(A ? B : ((C ? D : E) ? F : G))

Tài liệu tham khảo về Javascript

  1. Cool JavaScript Shortcuts and Tips for Everyday Use
  2. Hacks for Creating JavaScript Arrays
  3. JavaScript ES6: 5 new abstractions to improve your code
  4. ES6 Destructuring: The Complete Guide
  5. JavaScript typeof

** Lược dịch **

Glad Chinda, Conditional JavaScript for Experts, Medium