Giới thiệu về lập trình chức năng — Phần 2
Bài viết được dịch từ
Đây là phần hai trong 4 phần giởi thiệu về lập trình ‘chức năng’ trong Javascript. Ở phần trước, chúng ta đã thấy cách hàm được sử dụng làm đối số khi gọi tới một hàm khác, qua đó giúp tổ chức code gọn gàng, dễ bảo trì hơn. Ở bài hôm nay, chúng ta sẽ áp dụng kỹ thuật này cho các danh sách.
+ Phần 1: Các chất liệu và động lực
+ Phần 2: Làm việc với mảng và danh sách
+ Phần 4: Làm việc với phong cách
I. Làm việc với mảng và danh sách
Bài viết trước có đề cập tới nguyên tắc DRY — đừng lặp lại chính mình. Chúng ta sử dụng hàm để nhóm một bộ các hành động được lặp lại. Vậy bạn thấy sao nếu ta viết code gọi tới một hàm nhiều lần? Ví dụ:
Trong đoạn code này, addColour được lặp lại khá nhiều. Để tránh việc lặp, giải pháp là đưa danh sách các màu vào trong một mảng, rồi gọi addColourtrong vòng lặp for:
Gọn gàng hơn so với trước đó rồi nhỉ! Nhưng trông vẫn dài dòng khi phải đưa ra cho máy tính các chỉ thị thật cụ thể về việc tạo biến chỉ mục i, tăng nó lên, đồng thời kiểm tra xem khi nào thì kết thúc việc lặp. Có phải tốt hơn, nếu ta có thể gom tất cả những thứ ở vòng for vào một hàm?
1. For-Each
Vì Javascript cho phép dùng hàm làm tham số, ta có thể viết một hàm forEach tương đối đơn giản như sau:
Hàm này nhận hàm callback làm tham số và gọi nó mỗi khi lặp qua một phần tử của mảng.
Để thêm các màu trong danh sách, ta chỉ cần viết 1 dòng như sau:
💡 Hơn hết, Javascript trang bị sẵn cho tất cả mảng một phương thức có cùng tên và hoạt động giống như forEach viết ở trên:
Chú ý: forEach trở lại undefined.
Bạn có thể xem thêm về forEach ở trang
2. Map
Dù hàm forEach trên khá tiện lợi, tuy nhiên nó vẫn có điểm hạn chế. Nếu hàm callback được truyền vào có trở lại một giá trị, giá trị này sẽ bị forEachbỏ qua. Ta có thể sửa chút xíu ở forEach, để nhận được lại các giá trị mà hàm callback trả về.
Trước tiên quan sát ví dụ sau. Giả sử có một mảng các ID, ta muốn nhận lại được phần tử DOM tương ứng với mỗi một ID. Giải pháp thông thường là một vòng lặp for:
Một lần nữa, chúng ta nói với máy tính cách tạo chỉ mục, tăng giá trị của nó — Đây là chi tiết lẽ ra chúng ta không cần nghĩ tới. Hãy tổ chức lại code, đưa vòng for vào trong hàm tên là map, tương tự như cách được thực hiện với forEach:
Và sử dụng hàm map:
Khi thực thi, nó gọi hàm getElement trên mọi phần tử của mảng ids. Thay đổi không nhiều so với forEach, map đã có thể trả về các giá trị từ callback. Kết quả ”thu hoạch” được là một mảng mới, chứa những giá trị tương ứng với các giá trị của mảng ban đầu. 👨🌾🌾🌾🌾🌾
💡 Phương thức map cũng được Javascript cung cấp cho mảng tương tự forEach:
3. Reduce
Có thể thấy map vô cùng tiện lợi, nhưng ta còn có thể tạo ra một hàm tiện lợi hơn nữa 🔱, nếu cho phép đưa vào một mảng và trả về ‘chỉ’ một giá trị. 🤔 Hơi lăn tăn — Tại sao trở lại chỉ một lại tiện hơn trở lại nhiều giá trị? Để trả lời cho câu hỏi này, trước hết cần thấy cách hàm này hoạt động ra sao.
Để minh họa, hãy xem xét hai vấn đề giống nhau sau:
▪ Cho một mảng số, tính tổng của chúng.
▪ Cho một mảng các từ, nối chúng lại với nhau bởi dấu cách.
Đây có vẻ là ví dụ rất bình thường. Nhưng một khi bạn thấy được cách reduce hoạt động, chúng ta có thể áp dụng nó theo những cách thú vị hơn.
Ta vẫn sẽ bắt đầu với cách thông thường nhất, vòng for:
Hai lời giải trên có nhiều điểm chung. Chúng sử dụng vòng for để lặp qua mảng; đều có một biến kết quả (total và sentence); và khởi tạo giá trị ban đầu cho biến kết quả.
Giờ ta đưa phần bên trong mỗi vòng lặp vào một hàm:
Theo cách này, code dù chưa ngắn gọn nhưng phần nào cũng có tính tổ chức hơn. Hai hàm add và joinWord đều nhận biến kết quả làm tham số đầu tiên, mảng làm tham số thứ hai. Tiếp theo hãy dời vòng for ‘bừa bộn’ vào trong một hàm:
Và đây là lúc reduce tỏ ra thật tiện lợi:
💡 reduce được Javascript cung cấp cho các mảng tương tự forEach và map:
II. Kết hợp mọi thứ với nhau
Hai ví dụ trên đều rất bình thường — hàm add và joinWord hoàn toàn đơn giản. Nhưng đây là điểm vô cùng quan trọng: hàm càng nhỏ, càng đơn giản sẽ dễ dàng để nghĩ về và test cho nó. Khi trở nên quen thuộc với reduce, bạn sẽ thấy đọc code dễ hiểu hơn so với cách viết ban đầu.
▪ Tuy nhiên, như đề nói tới trước đó, chúng ta có thể làm được nhiều thứ phức tạp hơn là tính tổng các con số. Hãy bắt đầu với một đoạn dữ liệu có cấu trúc như bên dưới, rồi sử dụng map và reduce để chuyển đổi tới một mảng các thẻ
▪ Dữ liệu này trông không đẹp cho lắm. Mỗi mảng bên trong (chỗ tô đỏ) nên là một đối tượng có định dạng gọn gẽ hơn. Phần trên sử dụng reduce để tính toán các giá trị đơn giản như chuỗi và số, nhưng không ai yêu cầu giá trị mà hàm trở lại chỉ đơn giản như vậy. Chúng ta có thể sử dụng nó khi làm việc với đối tưởng, mảng hay thậm chí các thẻ DOM. Hãy tạo ra một hàm nhận một mảng bên trong (như [‘name’, ‘Fluttershy’]) và thêm cặp ‘khóa/giá trị’ cho một đối tượng:
▪ Sử dụng addToObject, chúng ta có thể chuyển đổi mỗi một ‘mảng pony’ sang dạng đối tượng:
▪ Nếu sau đó sử dụng map, ta có thể chuyển đổi mảng vào trong dạng gọn gàng hơn nữa:
Thế là chúng ta đã có một mảng các pony.
"..."
Hàm template có 2 tham số gồm một chuỗi (được định dạng) và một đối tượng. Bất kì chỗ nào trong chuỗi có dạng “ {tên-thuộc-tính}” như {name}, {image} thì được thay thế bởi thuộc tính của đối tượng. VD:
Mỗi một đối tượng pony ta sẽ có được một thẻ:
Đoạn code trên là cách để chuyển đổi riêng một phần tử sang dạng html, nhưng để thực hiện với toàn bộ mảng, ta sẽ cần tới reduce và joinWord:
Bạn có thể xem code khi kết hợp mọi thứ với nhau ở
Một khi hiểu được cách hoạt động của map và reduce, bạn có thể không còn sử dụng vòng for ‘cổ điển’ nữa. Sẽ có chút thách thức nếu bạn có thể tránh hoàn toàn sử dụng vòng for ở project tiếp theo của mình. Bên cạnh map và reduce, bạn có thể tìm hiểu thêm về các mẫu thức khác nhằm rút gọn code như
+
+
+
Tới đây, bạn đã thấy khả năng truyền hàm như đối số có ích ra sao, nhất là trong thao tác với các danh sách. Hãy từ từ vận dụng thành thạo các kĩ thuật này nhé!
Hãy tiếp tục ủng hộ và giữ kết nối với Vnknowledge các bạn nhé:
- Vnknowledge Page
- Vnknowledge Youtube
- Vnknowledge Patreon
Xin cảm ơn các bạn!