Mục lục
Mục lục
javascript Tiếp nối series “Javascript tuyển tập”, phần này chúng ta sẽ cùng nhau khám phá sâu hơn về giá trị Truthy và Falsy, các loại câu lệnh rẽ nhánh, và các loại vòng lặp quen thuộc trong JavaScript. Yên tâm là mấy cái này tuy cơ bản nhưng cực kỳ quan trọng, giúp code của bạn linh hoạt và mạnh mẽ hơn đó nha!
Ở phần trước, mình đã giới thiệu sơ qua về Truthy và Falsy. Để nhắc lại một chút, trong JavaScript, khi một giá trị không phải là boolean (true hoặc false) được sử dụng trong một ngữ cảnh mà trình thông dịch mong đợi một giá trị boolean (ví dụ: trong điều kiện của câu lệnh if), JavaScript sẽ tự động “ép” kiểu giá trị đó thành boolean. Những giá trị sau đây được coi là falsy (tức là khi ép kiểu sẽ thành false):
false (chắc chắn rồi!)
0 (số không)
-0 (số không âm)
0n (BigInt không)
“” (chuỗi rỗng)
null
undefined
NaN (Not a Number) Tất cả các giá trị còn lại đều là truthy (tức là khi ép kiểu sẽ thành true), ví dụ như:
Chuỗi không rỗng (ví dụ: “hello”, “0”, “false”)
Số khác không (ví dụ: 1, -1, 0.5)
Mảng (kể cả mảng rỗng [])
Đối tượng (kể cả đối tượng rỗng {})
Hàm (function() {})
true Vậy tác dụng của việc hiểu rõ truthy và falsy là gì? Nó giúp chúng ta viết code ngắn gọn và dễ đọc hơn khi kiểm tra các điều kiện. Thay vì viết if (myString.length > 0), bạn có thể viết gọn thành if (myString) vì một chuỗi rỗng là falsy, còn chuỗi có nội dung là truthy. Tiện lợi đúng không nào?
Câu lệnh rẽ nhánh cho phép chương trình của bạn thực hiện các hành động khác nhau tùy thuộc vào một điều kiện nhất định. Nó giúp code của bạn “thông minh” hơn, có thể phản ứng khác nhau trong các tình huống khác nhau. Trong JavaScript, có hai loại câu lệnh rẽ nhánh chính: if-else và switch case.
Câu lệnh if-else là cấu trúc rẽ nhánh cơ bản nhất. Nó cho phép bạn kiểm tra một điều kiện và thực hiện một khối code nếu điều kiện đó đúng (true), và một khối code khác (tùy chọn) nếu điều kiện đó sai (false). Bạn cũng có thể sử dụng else if để kiểm tra nhiều điều kiện liên tiếp. Cú pháp cơ bản:
if (điều_kiện) {
// Code thực hiện nếu điều_kiện là true
} else if (điều_kiện_khác) {
// Code thực hiện nếu điều_kiện_khác là true
} else {
// Code thực hiện nếu tất cả các điều_kiện trên đều là false
}
Ví dụ:
Câu lệnh switch case thường được sử dụng khi bạn muốn so sánh một biến với nhiều giá trị khác nhau và thực hiện các hành động tương ứng. Nó có thể giúp code của bạn trở nên gọn gàng hơn so với việc sử dụng nhiều câu lệnh if-else if liên tiếp, đặc biệt khi bạn có nhiều trường hợp cần kiểm tra. Cú pháp cơ bản:
switch (biểu_thức) {
case giá_trị_1:
// Code thực hiện nếu biểu_thức bằng giá_trị_1
break;
case giá_trị_2:
// Code thực hiện nếu biểu_thức bằng giá_trị_2
break;
// ... các trường hợp khác
default:
// Code thực hiện nếu biểu_thức không khớp với bất kỳ giá_trị nào ở trên
}
Lưu ý quan trọng là từ khóa break. Nếu bạn quên break sau mỗi case, chương trình sẽ tiếp tục thực hiện các case tiếp theo cho đến khi gặp break hoặc kết thúc câu lệnh switch. Điều này đôi khi hữu ích (fall-through), nhưng thường là nguyên nhân gây ra lỗi không mong muốn, nên hãy cẩn thận nha! Ví dụ:
Vòng lặp cho phép bạn thực hiện một khối code lặp đi lặp lại nhiều lần cho đến khi một điều kiện nhất định không còn đúng nữa. Vòng lặp rất hữu ích khi bạn cần xử lý một danh sách các phần tử (ví dụ: một mảng) hoặc thực hiện một tác vụ nào đó nhiều lần mà không cần phải viết đi viết lại đoạn code đó. Trong JavaScript có nhiều loại vòng lặp khác nhau, mỗi loại phù hợp với một số tình huống cụ thể.
Vòng lặp for: Đây là loại vòng lặp phổ biến nhất. Nó cho phép bạn xác định một biến khởi tạo, một điều kiện lặp, và một biểu thức tăng/giảm giá trị của biến đó sau mỗi lần lặp.
Cú pháp:
for (khởi_tạo; điều_kiện; tăng_giảm) {
// code thực thi
}
Ví dụ:
for (let i = 0; i < 5; i++) {
console.log(i); // In ra 0, 1, 2, 3, 4
}
Vòng lặp for…in: Vòng lặp này thường được sử dụng để lặp qua các thuộc tính (properties) của một đối tượng. Lưu ý rằng thứ tự lặp có thể không được đảm bảo và nó cũng sẽ lặp qua các thuộc tính kế thừa từ prototype, nên thường không được khuyến khích sử dụng cho mảng.
Cú pháp:
for (let biến in đối_tượng) {
// code thực thi
}
Ví dụ:
let user = { Name: "Doe", Age: 25 };
for (let key in user) {
console.log(key + ": " + user[key]);
}
Vòng lặp for…of: Vòng lặp này được giới thiệu trong ES6 và được sử dụng để lặp qua các giá trị của một đối tượng có thể lặp (iterable), ví dụ như mảng, chuỗi, Map, Set, v.v… Đây thường là lựa chọn tốt hơn để lặp qua các phần tử của mảng so với for truyền thống hoặc for…in.
Cú pháp:
for (let biến of mảng) {
// code thực thi
}
Ví dụ:
let colors = ["red", "green", "blue"];
for (let color of colors) {
console.log(color);
}
Vòng lặp for await…of: Đây là một phiên bản đặc biệt của for…of được sử dụng với các Promise trong ngữ cảnh async function. Nó cho phép bạn lặp qua một chuỗi các Promise và đợi cho mỗi Promise hoàn thành trước khi chuyển sang lần lặp tiếp theo.
Cú pháp:
for await (let biến of promiseKhởiTạo) {
// code thực thi
}
Ví dụ minh hoạ:
async function logData() {
let promises = [Promise.resolve("A"), Promise.resolve("B")];
for await (let val of promises) {
console.log(val); // In ra A, sau đó In ra B
}
}
Vòng lặp while: Vòng lặp while sẽ thực hiện một khối code trong khi một điều kiện nhất định vẫn đúng. Điều kiện được kiểm tra trước mỗi lần lặp. Nếu điều kiện ban đầu đã là false, khối code bên trong vòng lặp sẽ không được thực hiện lần nào.
Cú pháp:
while (điều_kiện) {
// code thực thi
}
Ví dụ:
let i = 0;
while (i < 5) {
console.log(i);
i++;
}
Vòng lặp do…while: Tương tự như while, nhưng vòng lặp do…while sẽ thực hiện khối code ít nhất một lần, sau đó mới kiểm tra điều kiện. Nếu điều kiện đúng, vòng lặp sẽ tiếp tục; nếu sai, vòng lặp sẽ kết thúc.
Cú pháp:
do {
// code thực thi
} while (điều_kiện);
Ví dụ:
let j = 0;
do {
console.log(j);
j++;
} while (j < 5);
break: Từ khóa break được sử dụng để thoát hoàn toàn khỏi vòng lặp ngay lập tức, bất kể điều kiện lặp có còn đúng hay không. Chương trình sẽ tiếp tục thực hiện các câu lệnh sau vòng lặp.
continue: Từ khóa continue được sử dụng để bỏ qua lần lặp hiện tại và chuyển sang lần lặp tiếp theo của vòng lặp. Các câu lệnh còn lại trong khối code của lần lặp hiện tại sẽ không được thực hiện. Ví dụ:
Hàm (Functions) là một khối code được đặt tên (hoặc không tên), được thiết kế để thực hiện một tác vụ cụ thể. Hàm giúp bạn tổ chức code một cách logic, tái sử dụng code nhiều lần mà không cần viết lại, và làm cho chương trình dễ đọc và bảo trì hơn. Cú pháp cơ bản để định nghĩa một hàm trong JavaScript:
function tenHam(thamSo1, thamSo2, ...) {
// Khối code thực hiện các thao tác của hàm
// Có thể có hoặc không có câu lệnh return để trả về một giá trị
return giaTriTraVe;
}
function: Từ khóa bắt đầu định nghĩa hàm.
tenHam: Tên của hàm (tùy chọn đối với hàm ẩn danh).
(thamSo1, thamSo2, …): Các tham số (đầu vào) mà hàm có thể nhận (tùy chọn).
{ … }: Khối code chứa các câu lệnh của hàm.
return giaTriTraVe;: Câu lệnh (tùy chọn) để trả về một giá trị từ hàm. Để gọi (sử dụng) một hàm, bạn chỉ cần viết tên hàm và theo sau là dấu ngoặc tròn () (và truyền các đối số nếu hàm có tham số):
let ketQua = tenHam(doiSo1, doiSo2);
Đây là loại hàm cơ bản nhất, có một tên rõ ràng khi được định nghĩa. Chúng ta đã thấy ví dụ ở trên. Ví dụ:
Hàm ẩn danh là hàm không có tên khi được định nghĩa. Chúng thường được gán cho một biến hoặc được truyền như một đối số cho một hàm khác. Ví dụ:
Đây là một loại hàm ẩn danh được định nghĩa và gọi ngay lập tức sau khi định nghĩa. Chúng thường được sử dụng để tạo ra một scope riêng biệt, tránh xung đột tên biến với code bên ngoài. Ví dụ:
Hàm lambda (arrow function) là một cú pháp ngắn gọn hơn để viết hàm ẩn danh, được giới thiệu trong ES6. Chúng có một số khác biệt so với hàm thông thường, đặc biệt là về cách xử lý this. Cú pháp:
(thamSo1, thamSo2, ...) => biểu_thức; // Trả về trực tiếp giá trị của biểu_thức
(thamSo1, thamSo2, ...) => {
// Nhiều câu lệnh
return giaTriTraVe;
};
Ví dụ:
Hàm callback là một hàm được truyền như một đối số cho một hàm khác, và sẽ được gọi (thực thi) sau đó, thường là sau khi một tác vụ bất đồng bộ hoàn thành hoặc khi một sự kiện xảy ra. Ví dụ:
Closure là một khái niệm nâng cao hơn trong JavaScript. Một closure xảy ra khi một hàm bên trong (inner function) có quyền truy cập vào các biến của hàm bên ngoài (outer function) ngay cả sau khi hàm bên ngoài đã thực thi xong. Điều này là do hàm bên trong “ghi nhớ” môi trường lexical (nơi nó được định nghĩa) của hàm bên ngoài. Ví dụ:
Đệ quy (Recursion) là một kỹ thuật lập trình trong đó một hàm tự gọi chính nó để giải quyết một bài toán. Để tránh việc gọi hàm vô hạn, một hàm đệ quy cần có một hoặc nhiều trường hợp cơ sở (base case), là điều kiện khi hàm không tự gọi nữa và dừng lại. Các trường hợp khác là bước đệ quy (recursive step), trong đó hàm tự gọi với một phiên bản nhỏ hơn của bài toán ban đầu. Một ví dụ kinh điển về đệ quy là tính giai thừa của một số nguyên dương (n!).
Trường hợp cơ sở: 0! = 1
Bước đệ quy: n! = n * (n-1)! với n > 0 Ví dụ:
Đệ quy có thể giúp giải quyết một số bài toán một cáchElegant và dễ hiểu, đặc biệt là các bài toán có cấu trúc lặp lại tự nhiên. Tuy nhiên, cần cẩn thận để đảm bảo có trường hợp cơ sở và bước đệ quy tiến gần đến trường hợp cơ sở, tránh tình trạng tràn bộ nhớ ngăn xếp (stack overflow) do gọi hàm đệ quy quá nhiều lần.
Vậy là chúng ta đã cùng nhau đi qua những khái niệm cơ bản nhưng vô cùng quan trọng về truthy/falsy, các loại câu lệnh rẽ nhánh giúp code linh hoạt hơn, và các loại vòng lặp để xử lý công việc lặp đi lặp lại một cách hiệu quả. Chúng ta cũng đã khám phá các kiểu định nghĩa hàm khác nhau và làm quen với giải thuật đệ quy thú vị. Nắm vững những kiến thức này sẽ giúp bạn tự tin hơn rất nhiều trên con đường chinh phục Javascript đó nha! Hẹn gặp lại các bạn ở phần tiếp theo với nhiều điều thú vị nữa!