Mục lục
Xem mục lục
Higher-Order Function là gì
Higher-Order Function (HOF) là một function thỏa ít nhất một trong hai điều kiện: nhận function khác làm tham số, hoặc trả về một function. Nhiều HOF thỏa cả hai cùng lúc.
function applyTwice(fn, value) {
return fn(fn(value));
}
applyTwice(x => x * 2, 3); // 12
applyTwice là HOF vì nhận fn làm tham số. Function truyền vào (x => x * 2) thường được gọi là callback.
First-class Function, điều kiện tiên quyết
HOF chỉ tồn tại được trong ngôn ngữ coi function là first-class citizen, tức function được đối xử như bất kỳ giá trị nào khác: gán được cho biến, lưu được trong mảng/object, truyền được làm tham số, và trả về được từ một function khác.
const greet = function () {
console.log("Xin chào");
};
const fns = [greet, () => console.log("Tạm biệt")];
fns[0](); // "Xin chào"
JavaScript hỗ trợ first-class function ngay từ đầu, đây là nền tảng khiến các pattern như HOF, callback, closure đều khả thi. Ở một ngôn ngữ không coi function là first-class, muốn truyền hành vi tùy chỉnh phải dùng cách gián tiếp hơn nhiều, ví dụ định nghĩa hẳn một interface hoặc class riêng cho việc đó.
Các HOF có sẵn quen thuộc
Nhiều method có sẵn trên Array.prototype là HOF, vì nhận callback làm tham số: map, filter, reduce, forEach, sort, find.
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
So với vòng lặp for thuần làm cùng việc:
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
map gói gọn phần “lặp qua mảng” bên trong, chỉ để lộ ra phần logic biến đổi từng phần tử (n => n * 2). Code ngắn hơn, và ý định (biến đổi từng phần tử thành giá trị mới) rõ ràng hơn so với đọc một vòng lặp thủ công.
Function trả về Function
Chiều còn lại của HOF: một function trả về function khác, thường được gọi là function factory.
function multiply(a) {
return function (b) {
return a * b;
};
}
const double = multiply(2);
const triple = multiply(3);
double(5); // 10
triple(5); // 15
multiply trả về một function mới mỗi lần gọi, function đó “nhớ” giá trị a đã truyền vào nhờ closure. Đây là chỗ HOF và closure thường đi cùng nhau: HOF cung cấp cơ chế trả về function, closure cung cấp cơ chế giữ lại trạng thái bên trong function được trả về.
Ứng dụng thực tế
Compose / Pipe
Kết hợp nhiều function nhỏ thành một function lớn, dữ liệu chảy qua từng bước:
const pipe =
(...fns) =>
input =>
fns.reduce((acc, fn) => fn(acc), input);
const process = pipe(
x => x + 1,
x => x * 2,
x => x - 3
);
process(5); // ((5 + 1) * 2) - 3 = 9
Decorator
Bọc thêm hành vi quanh một function có sẵn mà không sửa function gốc:
function withLogging(fn) {
return function (...args) {
console.log(`Gọi ${fn.name} với`, args);
return fn(...args);
};
}
const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3); // log ra tham số, rồi trả về 5
Middleware kiểu Express
Middleware trong Express là một dạng HOF: mỗi middleware nhận req, res, next (chính next cũng là một function được truyền vào), tạo thành chuỗi các function gọi lẫn nhau.
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
Đánh đổi cần lưu ý
HOF giúp code ngắn gọn và tách bạch rõ phần logic lặp/điều khiển khỏi phần logic nghiệp vụ, nhưng có vài đánh đổi:
- Debug khó hơn: stack trace của một chuỗi HOF lồng nhau (như
pipegồm nhiều bước) thường dài và khó đọc hơn một vòng lặp thuần, vì lỗi có thể nằm sâu bên trong một callback ẩn danh. - Hiệu năng: mỗi lần gọi
map,filter,reducetạo ra một lượt duyệt mảng riêng và có thể tạo mảng trung gian mới. Chain nhiều HOF liên tiếp trên mảng lớn tốn nhiều bộ nhớ và thời gian hơn một vòng lặpforgộp chung tất cả logic trong một lượt duyệt. - Closure giữ biến sống lâu hơn: vì HOF trả về function thường đi kèm closure, cần lưu ý rủi ro giữ tham chiếu không cần thiết, tương tự vấn đề đã nói ở phần closure.
Tổng kết
- Higher-Order Function là function nhận function khác làm tham số, hoặc trả về function, hoặc cả hai.
- HOF chỉ khả thi nhờ JavaScript coi function là first-class citizen.
map,filter,reduce,forEachlà các HOF quen thuộc, gói gọn phần lặp, để lộ phần logic biến đổi.- Ứng dụng thực tế phổ biến: compose/pipe, decorator, middleware.
- Đánh đổi cần cân nhắc: debug khó hơn với chain dài, chi phí hiệu năng khi chain nhiều HOF trên mảng lớn, và rủi ro closure giữ biến sống lâu hơn cần thiết.