Truthy và Falsy là gì? Tác dụng của nó?
Câu lệnh rẽ nhánh là gì?
Vòng lặp là gì?
Định nghĩa hàm, cú pháp hàm
Tóm cái váy lại!
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ư:
"hello"
, "0"
, "false"
)1
, -1
, 0.5
)[]
){}
)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?
<div id="truthy-falsy-output"></div>
<script>
let message = 'Hello!';
let emptyMessage = '';
let count = 0;
let myArray = [];
let myObject = {};
let output = '';
if (message) {
output += "<p>'Hello!' is truthy.</p>";
} else {
output += "<p>'Hello!' is falsy (wrong!).</p>";
}
if (emptyMessage) {
output += "<p>'' is truthy (wrong!).</p>";
} else {
output += "<p>'' is falsy.</p>";
}
if (count) {
output += '<p>0 is truthy (wrong!).</p>';
} else {
output += '<p>0 is falsy.</p>';
}
if (myArray) {
output += '<p>[] is truthy.</p>';
} else {
output += '<p>[] is falsy (wrong!).</p>';
}
if (myObject) {
output += '<p>{} is truthy.</p>';
} else {
output += '<p>{} is falsy (wrong!).</p>';
}
document.getElementById('truthy-falsy-output').innerHTML = output;
</script>
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ụ:
<div id="if-else-output"></div>
<script>
let temperature = 25;
let message = '';
if (temperature > 30) {
message = '05/05/2025 trời nóng quá!';
} else if (temperature > 20) {
message = 'Thời tiết 05/05/2025 khá dễ chịu.';
} else {
message = '05/05/2025 trời hơi se lạnh.';
}
document.getElementById('if-else-output').textContent = message;
</script>
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ụ:
<div id="switch-case-output"></div>
<script>
let dayOfWeek = new Date().getDay();
let dayName = '';
switch (dayOfWeek) {
case 1:
dayName = 'Thứ Hai';
break;
case 2:
dayName = 'Thứ Ba';
break;
case 3:
dayName = 'Thứ Tư';
break;
case 4:
dayName = 'Thứ Năm';
break;
case 5:
dayName = 'Thứ Sáu';
break;
case 6:
dayName = 'Thứ Bảy';
break;
case 7:
dayName = 'Chủ Nhật';
break;
default:
dayName = 'Không phải ngày trong tuần';
}
document.getElementById(
'switch-case-output'
).textContent = `05/05/2025 là ${dayName}.`;
</script>
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 hiện trong mỗi lần lặp
}
Ví dụ:
<div id="for-loop-output"></div>
<script>
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let output = 'Các số là: ';
for (let i = 0; i < numbers.length; i++) {
output += numbers[i] + ' ';
}
document.getElementById('for-loop-output').textContent = output;
</script>
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 (key in đối_tượng) {
// Code thực hiện với mỗi thuộc tính (key) của đối_tượng
}
Ví dụ:
<div id="for-in-loop-output"></div>
<script>
let person = { name: 'Dương', age: 20, city: 'Lào Cai' };
let output = 'Thông tin của tớ: ';
for (let key in person) {
output += key + ': ' + person[key] + ', ';
}
document.getElementById('for-in-loop-output').textContent = output.slice(
0,
-2
); // Loại bỏ dấu ", " cuối cùng
</script>
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 (value of đối_tượng_có_thể_lặp) {
// Code thực hiện với mỗi giá trị (value) của đối_tượng_có_thể_lặp
}
Ví dụ:
<div id="for-of-loop-output"></div>
<script>
let colors = ['red', 'green', 'blue'];
let output = 'Các màu là: ';
for (let color of colors) {
output += color + ' ';
}
document.getElementById('for-of-loop-output').textContent = output;
</script>
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:
async function processItems(items) {
for await (const item of items) {
// Xử lý item (có thể là một Promise)
console.log(item);
}
}
Ví dụ minh hoạ:
async function* generatePromises() {
yield new Promise((resolve) => setTimeout(() => resolve('Item 1'), 100));
yield new Promise((resolve) => setTimeout(() => resolve('Item 2'), 50));
yield new Promise((resolve) => setTimeout(() => resolve('Item 3'), 200));
}
async function process() {
for await (const item of generatePromises()) {
console.log(item); // Sẽ in ra "Item 1", sau đó "Item 2", sau đó "Item 3" theo thứ tự hoàn thành
}
}
// process();
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 hiện trong khi điều_kiện là true
}
Ví dụ:
<div id="while-loop-output"></div>
<script>
let count = 0;
let output = '';
while (count < 5) {
output += count + ' ';
count++;
}
document.getElementById('while-loop-output').textContent = `Đếm: ${output}`;
</script>
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 hiện ít nhất một lần, và tiếp tục nếu điều_kiện là true
} while (điều_kiện);
Ví dụ:
<div id="do-while-loop-output"></div>
<script>
let count = 0;
let output = '';
do {
output += count + ' ';
count++;
} while (count < 3);
document.getElementById(
'do-while-loop-output'
).textContent = `Đếm (do-while): ${output}`;
</script>
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ụ:
<div id="break-continue-output"></div>
<script>
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let breakOutput = 'Sử dụng break (chỉ in đến số 5): ';
for (let number of numbers) {
if (number > 5) {
break;
}
breakOutput += number + ' ';
}
let continueOutput = 'Sử dụng continue (bỏ qua số chẵn): ';
for (let number of numbers) {
if (number % 2 === 0) {
continue;
}
continueOutput += number + ' ';
}
document.getElementById('break-continue-output').innerHTML = `
<p>${breakOutput}</p>
<p>${continueOutput}</p>
`;
</script>
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ụ:
<div id="named-function-output"></div>
<script>
function add(a, b) {
return a + b;
}
let sum = add(5, 3);
document.getElementById(
'named-function-output'
).textContent = `Tổng là: ${sum}`;
</script>
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ụ:
<div id="anonymous-function-output"></div>
<script>
let multiply = function (a, b) {
return a * b;
};
let product = multiply(4, 6);
document.getElementById(
'anonymous-function-output'
).textContent = `Tích là: ${product}`;
</script>
Đâ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ụ:
<div id="iife-output"></div>
<script>
(function () {
let message = 'Chào mừng các bạn từ IIFE!';
document.getElementById('iife-output').textContent = message;
})();
// Thử truy cập biến message ở ngoài sẽ gây lỗi vì nó chỉ tồn tại trong scope của IIFE
// console.log(message); // Error: message is not defined
</script>
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ụ:
<div id="arrow-function-output"></div>
<script>
let divide = (a, b) => a / b;
let greet = (name) => {
return `Xin chào, ${name}!`;
};
let result = divide(10, 2);
let greeting = greet('các bạn');
document.getElementById('arrow-function-output').innerHTML = `
<p>10 / 2 = ${result}</p>
<p>${greeting}</p>
`;
</script>
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ụ:
<div id="callback-function-output"></div>
<script>
function doSomething(callback) {
setTimeout(function () {
let result = 'Tác vụ hoàn thành!';
callback(result); // Gọi hàm callback với kết quả
}, 1000);
}
function handleResult(message) {
document.getElementById('callback-function-output').textContent = message;
}
doSomething(handleResult); // Truyền handleResult làm callback cho doSomething
</script>
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ụ:
<div id="closure-output"></div>
<script>
function outerFunction(outerVar) {
function innerFunction(innerVar) {
return `Outer variable: ${outerVar}, Inner variable: ${innerVar}`;
}
return innerFunction;
}
let getMessage = outerFunction('Hello các bạn from outer!');
let message = getMessage('Data from inner.');
document.getElementById('closure-output').textContent = message;
</script>
Đệ 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!).
0! = 1
n! = n * (n-1)!
với n > 0
Ví dụ:
<div id="recursion-output"></div>
<script>
function factorial(n) {
if (n === 0) {
return 1; // Trường hợp cơ sở
} else {
return n * factorial(n - 1); // Bước đệ quy
}
}
let result = factorial(5); // Tính 5! = 5 * 4 * 3 * 2 * 1 = 120
document.getElementById('recursion-output').textContent = `5! = ${result}`;
</script>
Đệ 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!