Bài trước: Web back-end (7) - Local và Global, Dependencies và devDependencies
-----
8. Một số chủ đề JavaScript (1)
Chúng ta cùng quay trở lại ứng dụng web TeoShop.
Quan sát tập tin mã nguồn của index.js.
[index.js]
'use
strict'
const
express = require('express')
const app
= express();
const
port = process.env.PORT || 9000
//
xu ly khi nguoi dung gui request toi web server
app.get("/",
(req, res) => {
res.send('Chao
ban den voi TeoShop!');
})
//
khoi dong web server
app.listen(port,
() => {
console.log(`server
dang chay tren cong ${port}`);
})
Để hiểu được mã JavaScript trong tập tin index.js và có kiến thức để lập trình các phần tiếp theo, chúng ta cùng tìm hiểu một số chủ đề về lập trình JavaScript.
8.1 Lập trình đồng bộ trong JavaScript
Chúng ta sẽ bắt đầu với khái niệm lập trình đồng bộ.
Trong JavaScript, lập trình đồng bộ (synchronous programming) là kỹ thuật lập trình mà các lệnh (statements) được thực thi tuần tự, theo thứ tự từ trên xuống dưới. Mỗi lệnh phải hoàn thành trước khi lệnh tiếp theo được thực thi. Điều này có nghĩa là nếu một tác vụ mất nhiều thời gian (như đọc tập tin, gọi API, giao tiếp client-server), thì toàn bộ chương trình sẽ bị
"chặn" (blocked) cho đến khi tác vụ đó hoàn tất.
Đặc điểm của lập trình đồng bộ
- Thứ tự thực thi rõ ràng: các lệnh chạy theo thứ tự được viết trong mã nguồn
- Chặn luồng (blocking): một lệnh chưa xong thì lệnh tiếp theo không thể chạy
- Phù hợp với tác vụ đơn giản: các tác vụ không cần chờ đợi (như tính toán cơ bản) thường được xử lý đồng bộ
Chúng ta cùng lập trình và quan sát một số ví dụ
Ví dụ 1. Một tính toán căn bản
- Tạo tập tin tinhToanCanBan.js bằng phần mềm viết mã bất kỳ
- Nhập vào đoạn mã sau
[tinhToanCanBan.js]
function
cong(a, b) {
return
a + b;
}
console.log("Bắt
đầu");
let
kq = cong(6,8);
// thực thi đồng bộ
console.log("Kết
quả:", kq);
console.log("Kết
thúc");
Mở cửa sổ dòng lệnh, sử dụng node để chạy tập tin tinhToanCanBan.js.
E:\TeoShop_Test\bat-dong-bo>node
tinhToanCanBan.js
Bắt đầu
Kết quả: 14
Kết thúc
Bạn sẽ thấy các lệnh chạy theo thứ tự: in chữ "Bắt đầu" ra màn hình > tính cong(6, 8) > in "Kết quả: 14" > rồi in "Kết thúc". Không có hiện tượng xáo trộn các lệnh khi thực thi, mọi thứ diễn ra tuần tự.
Ví dụ 2. Vòng lặp đồng bộ
[vongLap.js]
function
xuLyDaySo(){
console.log("Bắt
đầu xử lý ...");
for(let i
= 1; i <= 5;
i++){
console.log("Số:",
i);
}
console.log("Xử
lý xong!");
}
xuLyDaySo();
console.log("Tiếp
tục công việc khác");
Kết quả khi chạy:
E:\TeoShop_Test\bat-dong-bo>node
vongLap.js
Bắt đầu xử lý ...
Số: 1
Số: 2
Số: 3
Số: 4
Số: 5
Xử lý xong!
Tiếp tục công việc khác
Bạn sẽ thấy: vòng lặp for chạy đồng bộ: in từng số, từ 1 đến 5 trước khi tiếp tục công việc khác. Chỉ khi hàm xuLyDaySo() hoàn tất, dòng "Tiếp tục công việc khác" mới được in.
Ví dụ 3. Giả lập tác vụ có tình huống chặn luồng chương trình (khóa chương trình) (blocking)
[chanLuong.js]
//
chặn luồng
function
chanLuong(){
console.log("Bắt
đầu tác vụ cần nhiều thời gian xử lý");
let batDau
= Date.now();
while(Date.now()
- batDau < 5000){
//
Giả lập chờ 5 giây bằng vòng lặp
}
console.log("Tác
vụ chạy 5 giây hoàn tất")
}
console.log("Trước
khi chạy tác vụ");
chanLuong()
// chặn luồng 5 giây
console.log("Sau
khi chạy tác vụ");
Kết quả khi chạy:
E:\TeoShop_Test\bat-dong-bo>node
chanLuong.js
Trước khi chạy tác vụ
Bắt đầu tác vụ cần nhiều thời
gian xử lý
Tác vụ chạy 5 giây hoàn tất
Sau khi chạy tác vụ
Bạn sẽ thấy: hàm chanLuong() giả lập một tác vụ mất 5 giây bằng cách chặn luồng. Dòng thông báo "Sau khi chạy tác vụ" chỉ được in, sau khi chayLuong() hoàn tất, vì mọi thứ chạy đồng bộ, mặc dù chương trình dừng lại rất lâu nhưng không có xử lý khác được chèn vào.
8.2 Hàm ẩn danh
Hàm ẩn danh (anonymous function) là một hàm không có tên (không được đặt tên) khi định nghĩa. Thay vì khai báo với từ khóa function đi kèm với tên như hàm thông thường, hàm ẩn danh thường được gán trực tiếp vào biến, truyền làm tham số, hoặc thực thi ngay lập tức mà không cần gọi hàm.
Đặc điểm của hàm ẩn danh:
- Không có tên: được định nghĩa mà không cần đặt tên sau từ khóa function
- Tính linh hoạt: thường được dùng ngay tại nơi khai báo hoặc gán vào biến
- Phạm vi: có thể truy cập biến trong phạm vi bao quanh (closure)
- Không hoisting: không được "nâng lên" (hoisted) như hàm khai báo (function declaration)
Hàm ẩn danh được dùng để xử lý các tác vụ ngắn gọn, trong callback, sự kiện, hoặc khi không cần tái sử dụng hàm.
Cú pháp
Hàm ẩn danh thông thường:
function() {
// thân hàm
}
Hàm ẩn danh kiểu mũi tên:
() => {
// thân hàm
}
Một số ví dụ về cách dùng hàm ẩn danh
Ví dụ 1: gán hàm ẩn danh cho một biến. Vì JavaScript coi hàm cũng là đối tượng, nên bạn có thể dùng biến để chứa nội dung của hàm.
const
cong = function(x,y){
return
x + y;
}
console.log(cong(2,4));
//6
Hàm ẩn danh function(x,y) được gán vào biến cong.
Sau đó được gọi thực thi như một hàm thông thường.
Ví dụ 2: dùng hàm ẩn danh như là tham số để truyền vào hàm khác (callback)
setTimeout(function()
{
console.log("Đã
hết 3 giây! (sau 3 giây)");
},
3000);
console.log("Đang
chờ");
Kết quả chạy:
Đang
chờ
Đã
hết 3 giây! (sau 3 giây)
Ví dụ 3: định nghĩa hàm ẩn danh bằng cú pháp mũi tên (arrow function)
setTimeout(()
=> {
console.log("Đã
hết 4 giây! (sau 4 giây)");
},
4000);
console.log("Đang
chờ");
Kết quả chạy:
Đang
chờ
Đã
hết 4 giây! (sau 4 giây)
Ví dụ 4: sử dụng trong phương thức của mảng. Hàm ẩn danh được dùng làm tham số, để truyền cho phương thức của mảng.
const
numbers = [1, 2, 3, 4];
numbers.forEach(function(num)
{
console.log(num
+ 1);
});
//
Kết quả: 2, 3, 4, 5
Viết lại theo kiểu arrow function:
const
numbers = [1, 2, 3, 4];
numbers.forEach(num
=> console.log(num + 1));
//
Kết quả: 2, 3, 4, 5
Ví dụ 5: hàm ẩn danh có thể thực thi ngay sau khi định nghĩa. (Immediately Invoked Function Expression - IIFE).
Cú pháp:
(function() { ... })()
Ví dụ:
(function() {
console.log("Hàm
chạy ngay sau khi định nghĩa, không cần thao tác gọi hàm!");
})();
Tuy nhiên, hàm ẩn danh cũng có một số hạn chế như:
- Khó gỡ lỗi (debug); vì không có tên, việc xác định lỗi trong stack trace có thể khó khăn hơn.
- Không tái sử dụng được: nếu cần dùng lại, bạn phải gán nó cho một biến hoặc đặt tên
8.3 Bài tập
Bài tập 8.1 Cài đặt các ví dụ trong bài học.
Câu 8.2: Lập trình đồng bộ trong JavaScript là gì? Phát biểu nào sau đây không đúng?
A. Lập trình đồng bộ không chặn luồng, cho phép các lệnh tiếp theo chạy ngay cả khi tác vụ chưa hoàn tất.
B. Các lệnh được thực thi tuần tự, theo thứ tự từ trên xuống dưới.
C. Mỗi lệnh phải hoàn thành trước khi lệnh tiếp theo được thực thi.
D. Phù hợp với các tác vụ đơn giản như tính toán cơ bản không cần chờ đợi.
Câu 8.3: Hàm ẩn danh (anonymous function) trong JavaScript là gì? Phát biểu nào sau đây không đúng?
A. Hàm ẩn danh có thể truy cập biến trong phạm vi bao quanh nhờ closure.
B. Hàm ẩn danh là hàm không có tên khi được định nghĩa.
C. Hàm ẩn danh thường được gán vào biến hoặc truyền làm tham số cho hàm khác.
D. Hàm ẩn danh được "nâng lên" (hoisted) giống như hàm khai báo (function declaration).
Cập nhật: 10/3/2025
-----
Bài sau: Web back-end (9) - Một số chủ đề JavaScript (2)