Web back-end (9) - Một số chủ đề JavaScript (2)

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