Bài trước: Web back-end (8) - Một số chủ đề JavaScript (1)
-----
9. Một số chủ đề JavaScript (2)
Ở phần trước, chúng ta đã tìm hiểu về lập trình đồng bộ trong JavaScript, hàm ẩn danh. Chúng ta sẽ tìm hiểu tiếp một số chủ đề liên quan đến lập trình trong JavaScript.
9.1 Hàm mũi tên (arrow function)
Ở phần trước bạn
cũng đã được làm quen với hàm mũi tên, phần này chúng ta sẽ tìm hiểu kỹ hơn.
Hàm mũi tên là
một tính năng quan trọng được giới thiệu trong ES6 (ECMAScript 2015). Đây là
cách viết hàm ngắn gọn và hiện đại trong JavaScript, thường được dùng trong lập
trình bất đồng bộ và các tình huống khác.
Hàm mũi tên là
một cách định nghĩa hàm trong JavaScript, thay thế cho cú pháp hàm truyền thống
(function declaration/expression). Nó sử dụng ký hiệu => (mũi tên) để kết
nối tham số và thân hàm, mang lại sự ngắn gọn và một số đặc tính độc đáo về ngữ
nghĩa.
Hàm mũi tên giúp
viết mã ngắn hơn, dễ đọc hơn, đặc biệt trong các callback hoặc hàm bất đồng bộ.
Sự khác biệt quan
trọng của hàm mũi tên chính là cách xử lý từ khóa this (ngữ cảnh) so với hàm
thông thường.
Đặc điểm
của hàm mũi tên
- Cú pháp ngắn
gọn: do loại bỏ từ khóa function, dấu {} và lệnh return trong một số trường hợp
- Không có this
(ngữ cảnh) riêng: this trong hàm mũi tên được kế thừa từ phạm vi bên ngoài
(lexical scoping), không bị ràng buộc (bound) như hàm thông thường
- Không có
arguments: không tự động tạo biến arguments để truy cập danh sách tham số
- Không thể dùng
làm hàm tạo (constructor): không thể gọi với từ khóa new để tạo đối
tượng
Cú pháp của
hàm mũi tên
Cú pháp chung của
hàm mũi tên là sử dụng ký hiệu => (mũi tên) để kết nối tham số và thân hàm.
Có 3 cách để viết
hàm mũi tên:
- [1] Không tham
số: () => { ... }
- [2] Một tham
số: x => { ... } (không cần dấu ngoặc cho một tham số)
- [3] Nhiều tham
số: (x, y) => { ... }
Một số ví
dụ
[1] Hàm không
có tham số
const thongBao = () => {
console.log("Chưa có thông báo, khi nào có
thông báo sẽ thông báo!");
}
thongBao();
Tình huống: áp
dụng trong hàm setTimeout()
setTimeout(() => {
console.log("Đã
hết 4 giây! (sau 4 giây)");
}, 4000);
console.log("Đang
chờ");
[2] Hàm có một
tham số: x => { ... }
Không cần dấu
ngoặc cho hàm chỉ có một tham số.
Hàm mũi tên có
thể tự động trả về kết quả, mà không cần lệnh return.
const binhPhuong = x => x * x;
console.log(binhPhuong(3)); //
9
Tình huống: áp
dụng trong duyệt mảng
const numbers = [1, 2, 3, 4];
numbers.forEach(num =>
console.log(num * num));
// Kết quả: 1, 4, 9, 16
[3] Nhiều có
tham số: (x, y) => { ... }
const cong = (x,y) => x + y;
console.log(cong(2,4)); //6
Do thân hàm chỉ
có một dòng lệnh, nên không cần dấu {}. Hàm tự động trả về kết quả, mà không
cần lệnh return.
Từ khóa
this trong hàm mũi tên
Như đã đề cập,
hàm mũi tên không tạo this (biến ngữ cảnh) riêng, mà lấy this từ phạm vi bao
quanh.
Ví dụ:
function Person() {
this.age = 0;
setInterval(()
=> {
this.age++; // 'this' tham chiếu đến đối tượng Person
console.log(this.age);
}, 1000);
}
const p = new Person(); // Kết quả: 1, 2, 3, ... (tăng mỗi giây)
Nếu dùng hàm
thông thường (function() { ... }), this sẽ trỏ đến setInterval thay vì Person,
gây lỗi. Hàm mũi tên khắc phục vấn đề này.
Mặc dù hàm mũi tên có nhiều ưu điểm như trên. Tuy nhiên, hàm mũi tên không phải lúc nào cũng phù hợp. Trong một số trường hợp, chẳng hạn như khi bạn cần sử dụng đối tượng arguments hoặc khi bạn cần sử dụng hàm làm hàm tạo, bạn nên sử dụng hàm truyền thống. Hoặc khi cần sự rõ ràng trong mã nguồn, hãy dùng function thông thường.
9.2 Lập trình bất đồng bộ trong JavaScript
Nhắc lại một chút
về 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.
Ví dụ, một chương
trình lập trình theo kiểu đồng bộ.
// 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ụ");
JavaScript là một
ngôn ngữ đơn luồng, nghĩa là nó chỉ có thể thực hiện một tác vụ tại một thời
điểm. Nếu một tác vụ mất nhiều thời gian để hoàn thành, mà chúng ta lại lập
trình theo kiểu đồng bộ, thì nó sẽ chặn luồng chính và làm cho ứng dụng không
phản hồi.
Lập trình bất
đồng bộ giải quyết vấn đề này bằng cách cho phép chương trình tiếp tục thực
hiện các tác vụ khác, trong khi chờ đợi tác vụ tốn thời gian hoàn thành.
Lập trình
bất đồng bộ là gì?
Trong JavaScript,
lập trình bất đồng bộ (asynchronous programming) là một mô hình lập trình cho
phép chương trình thực hiện nhiều tác vụ cùng lúc mà không cần chờ đợi tác vụ
trước đó hoàn thành. Điều này đặc biệt hữu ích khi xử lý các tác vụ tốn thời
gian như yêu cầu xử lý qua mạng (gọi API, giao tiếp client-server), đọc/ghi tập
tin hoặc tương tác với cơ sở dữ liệu.
Các kỹ
thuật lập trình bất đồng bộ
[1] Callback
- Đây là cách
tiếp cận truyền thống để xử lý bất đồng bộ trong JavaScript.
- Một callback là
một hàm được truyền vào một hàm khác và được thực thi khi tác vụ bất đồng bộ
hoàn thành.
- Tuy nhiên, sử
dụng quá nhiều callback có thể dẫn đến "callback hell", khiến mã
nguồn trở nên khó đọc và khó bảo trì.
[2] Promises
- Promises là một
đối tượng đại diện cho kết quả cuối cùng của một tác vụ bất đồng bộ.
- Chúng cung cấp
một cách viết mã bất đồng bộ rõ ràng và dễ bảo trì hơn so với callback.
- then() được sử
dụng khi thực hiện thành công, catch() được sử dụng khi gặp lỗi.
[3] Async/await:
- Async/await là
một cú pháp mới hơn để viết mã bất đồng bộ, được giới thiệu trong ES2017.
- Nó cho phép bạn
viết mã bất đồng bộ trông giống như mã đồng bộ, giúp mã trở nên dễ đọc và dễ
hiểu hơn.
- async được đặt trước function, await được đặt trước các lời gọi hàm bất đồng bộ.
9.3 Bài tập
Bài tập 9.1 Cài
đặt các ví dụ trong bài học.
Câu 9.2: Hàm mũi
tên (arrow function) là gì? Phát biểu nào sau đây không đúng về hàm mũi tên?
A. Hàm mũi tên có
cú pháp ngắn gọn, giúp viết mã dễ đọc hơn.
B. Hàm mũi tên có
this (ngữ cảnh) riêng, không kế thừa từ phạm vi bên ngoài.
C. Hàm mũi tên
không thể được dùng làm hàm tạo (constructor).
D. Hàm mũi tên
không có biến arguments để truy cập danh sách tham số.
Câu 9.3: Lập
trình bất đồng bộ là gì? Phát biểu nào sau đây không đúng về lập trình bất đồng
bộ?
A. Lập trình bất
đồng bộ cho phép chương trình thực hiện nhiều tác vụ cùng lúc mà không cần chờ
đợi tác vụ trước đó hoàn thành.
B. Lập trình bất
đồng bộ đặc biệt hữu ích khi xử lý các tác vụ tốn thời gian như yêu cầu mạng,
đọc/ghi tập tin hoặc tương tác với cơ sở dữ liệu.
C. Callback là
một kỹ thuật bất đồng bộ, nó sẽ làm cho code dễ đọc và dễ bảo trì hơn khi code
có nhiều callback lồng nhau.
D. Async/await
cho phép viết mã bất đồng bộ trông giống như mã đồng bộ, giúp mã trở nên dễ đọc
và dễ hiểu hơn.
-----
Cập nhật: 10/3/2025
-----
Bài sau: Web back-end (10) - Hàm callback