Trong JavaScript, các chức năng được coi là công dân hạng nhất. Chúng ta có thể coi các hàm là các giá trị và gán chúng cho một biến khác, chuyển chúng làm đối số cho một hàm khác hoặc thậm chí trả về chúng từ một hàm khác.
Khả năng này của các hàm hoạt động như các hàm hạng nhất là thứ cung cấp năng lượng cho các hàm bậc cao hơn trong JavaScript.
Về cơ bản, một hàm lấy một hàm khác làm đối số hoặc trả về một hàm được gọi là hàm bậc cao hơn.

Hãy tìm hiểu sâu hơn một chút để xem cả hai loại triển khai, đó là:
- Truyền một hàm làm đối số cho một hàm khác
- Trả về một hàm từ một hàm khác

Cách truyền một hàm làm đối số cho một hàm khác
Trong phần này, chúng ta sẽ xem cách chúng ta có thể gửi một hàm dưới dạng đối số và cuối cùng là cách nó giúp chúng ta viết mã sạch hơn.
Xem xét đoạn mã sau mà chúng ta muốn tạo một hàm chấp nhận một mảng làm đối số. Nó lọc ra tất cả các số lẻ từ nó và trả về tất cả các số đã lọc.
Chức năng sẽ trông giống như thế này:
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
function filterOdd(arr) {
const filteredArr = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 !== 0) {
filteredArr.push(arr[i]);
}
}
return filteredArr;
}
console.log(filterOdd(arr));
// Output:
// [ 1, 3, 5, 7, 9, 11 ]
Hàm trên trả về mảng đã lọc [ 1, 3, 5, 7, 9, 11 ]
nơi chúng tôi có tất cả các số lẻ, như mong đợi.
Bây giờ, giả sử chúng ta cũng muốn tạo một hàm lọc ra và trả về tất cả các số chẵn. Chúng ta rất có thể tiếp tục và tạo chức năng sau để đạt được điều này:
function filterEven(arr) {
const filteredArr = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 == 0) {
filteredArr.push(arr[i]);
}
}
return filteredArr;
}
console.log(filterEven(arr));
// Output:
// [ 2, 4, 6, 8, 10 ]
Một lần nữa, như mong đợi, chúng ta sẽ nhận được đầu ra mong muốn của một mảng có tất cả các số chẵn trong đó – [ 2, 4, 6, 8, 10 ]
.
Nhưng lưu ý rằng chúng tôi đang viết rất nhiều mã trùng lặp theo phương pháp này. Cả hai hàm trên đều thực hiện rất nhiều việc chung, chẳng hạn như chấp nhận mảng ban đầu, tạo một mảng mới để lưu trữ mảng đã lọc, lặp qua toàn bộ mảng chính và cuối cùng trả về mảng đã lọc.
Sự khác biệt duy nhất giữa cả hai chức năng là logic mà chúng sử dụng để lọc ra mảng ban đầu.
Đối với chức năng filterOdd
chúng tôi sử dụng logic của arr[i] % 2 !== 0
trong khi ở filterEven
chức năng chúng tôi sử dụng logic arr[i] % 2 == 0
để lọc ra mảng ban đầu.
Đây là nơi chúng ta có thể hưởng lợi từ việc sử dụng các hàm bậc cao hơn. Mục đích chính là tạo một hàm để thực hiện tất cả những thứ chung mà chúng ta đã làm trong hai hàm trên và chuyển riêng phần logic làm đối số cho hàm này. Hãy xem làm thế nào chúng ta có thể thực hiện điều này.
Hãy tạo một hàm thực hiện tất cả những thứ phổ biến mà chúng ta đã thực hiện trong filterOdd
và filterEven
chức năng. Điều này sẽ đi một cái gì đó như thế này:
function filterFunction(arr, callback) {
const filteredArr = [];
for (let i = 0; i < arr.length; i++) {
callback(arr[i]) ? filteredArr.push(arr[i]) : null;
}
return filteredArr;
}
Bỏ qua callback
tham số cho bây giờ. Chú ý làm thế nào trong cái mới filterFuntion
chúng tôi giữ tất cả các bước chung, đó là chấp nhận mảng ban đầu, tạo một mảng mới để lưu trữ mảng đã lọc, lặp qua toàn bộ mảng chính và cuối cùng trả về mảng đã lọc mà chúng tôi đã thực hiện trong filterOdd
và filterEven
chức năng.
Bây giờ callback
tham số về cơ bản chấp nhận logic sẽ không là gì ngoài một chức năng khác chứa logic lọc. Để lọc các số lẻ và số chẵn tương ứng, đây là các hàm logic chúng ta cần viết:
// Function containing logic for filtering out odd numbers
function isOdd(x) {
return x % 2 != 0;
}
// Function containing logic for filtering out even numbers
function isEven(x) {
return x % 2 === 0;
}
Đó là nó! Bây giờ chúng ta chỉ cần truyền mảng chính, cùng với hàm logic cho filterFunction
như thế này:
// For filtering out odd numbers
filterFunction(arr, isOdd)
// Output of console.log(filterFunction(arr, isOdd)):
// [ 1, 3, 5, 7, 9, 11 ]
// For filtering out even numbers
filterFunction(arr, isEven)
// Output of console.log(filterFunction(arr, isEven)):
// [ 2, 4, 6, 8, 10 ]
Bằng cách này, chúng ta đang chuyển các hàm logic như isOdd
hoặc isEven
làm đối số cho hàm khác filterFunction
.
Về cơ bản, chúng tôi đang trừu tượng hóa logic lọc chính từ chức năng chính. Bây giờ chúng ta có thể chuyển bất kỳ logic lọc nào khác theo ý muốn filterFunction
mà không cần phải thay đổi nó.
Ví dụ chúng ta muốn lọc ra một số lớn hơn 5 thì chỉ cần viết logic lọc như sau:
function isGreaterThanFive(x) {
return x > 5;
}
và chuyển nó làm đối số cho filterFunction
:
filterFunction(arr, isGreaterThanFive)
// Output of console.log(filterFunction(arr, isGreaterThanFive)):
// [ 6, 7, 8, 9, 10, 11 ]
Chúng ta cũng có thể truyền hàm logic dưới dạng hàm mũi tên và nhận được kết quả tương tự – nghĩa là truyền (x) => x > 5)
thay cho isGreaterThanFive
sẽ cho chúng ta kết quả tương tự.
filterFunction(arr, (x) => x > 5)
// Output of console.log(filterFunction(arr, (x) => x > 5)):
// [ 6, 7, 8, 9, 10, 11 ]
Cách tạo Polyfill
Chúng ta biết rằng JavaScript cung cấp cho chúng ta một số hàm bậc cao có sẵn như map()
, filter()
, reduce()
và như thế. Chúng ta có thể tạo lại việc thực hiện các chức năng này của riêng mình không? Hãy tìm hiểu sâu hơn một chút.
Chúng tôi đã tạo chức năng lọc của mình trong phần trên. Hãy tạo một nguyên mẫu mảng của chúng tôi filterFunction
để chúng ta có thể sử dụng nó với bất kỳ mảng nào. Điều này sẽ trông giống như thế này:
Array.prototype.filterFunction = function (callback) {
const filteredArr = [];
for (let i = 0; i < this.length; i++) {
callback(this[i]) ? filteredArr.push(this[i]) : null;
}
return filteredArr;
};
Trong đoạn mã trên, this
đề cập đến mảng mà nguyên mẫu được gọi. Vì vậy, nếu chúng ta viết một cái gì đó như:
const arr = [1, 2, 3, 4, 5]
arr.filterFunction(callbackFn)
sau đó this
sẽ đề cập đến mảng arr
.
Bây giờ chúng ta có thể sử dụng filterFunction
giống như chúng tôi sử dụng sẵn có filter()
chức năng trong JS. Chúng ta có thể viết một cái gì đó như thế này:
arr.filterFunction(isEven)
tương tự như gọi sẵn filter()
chức năng:
arr.filter(isEven)
Cả hai lệnh gọi hàm trên (nghĩa là arr.filterFunction(isEven)
và arr.filter(isEven)
) sẽ cung cấp cho chúng tôi cùng một đầu ra, như [ 2, 4, 6, 8, 10 ]
.
Tương tự, chúng ta cũng có thể chuyển một hàm mũi tên trong quá trình triển khai nguyên mẫu của mình vì chúng tôi có thể chuyển trong hàm có sẵn filter()
chức năng.
// I
arr.filterFunction((x) => x % 2 != 0)
arr.filter((x) => x % 2 != 0)
// both give the same output on console.log: [ 1, 3, 5, 7, 9, 11 ]
// II
arr.filterFunction((x) => x > 5)
arr.filter((x) => x > 5)
// both give the same output on console.log: [ 6, 7, 8, 9, 10, 11 ]
Theo một cách nào đó, chúng tôi đã viết một polyfill cho sẵn có filter()
chức năng.
Chuỗi chức năng
Chúng tôi cũng có thể triển khai chức năng xâu chuỗi với triển khai nguyên mẫu của mình giống như chúng tôi có thể thực hiện với chức năng sẵn có filter()
chức năng. Đầu tiên hãy lọc ra tất cả các số lớn hơn 5. Sau đó, từ kết quả, chúng ta sẽ lọc ra tất cả các số chẵn. Nó sẽ trông giống như thế này:
// Using our own filterFunction() prototype implementation
arr.filterFunction((x) => x > 5).filterFunction((x) => x % 2 === 0)
//Using the inbuilt filter() implementation
arr.filter((x) => x > 5).filter((x) => x % 2 === 0)
// both give the same output on console.log: [ 6, 8, 10 ]
Đây là cách chúng ta có thể sử dụng các hàm bậc cao hơn trong JS để viết mã theo chế độ mô-đun, sạch hơn và dễ bảo trì hơn.
Tiếp theo, hãy xem cách chúng ta có thể trả về một hàm từ một hàm khác.

Cách trả về một hàm từ một hàm khác trong JavaScript
Chúng ta có thể trả về một hàm từ một hàm khác vì chúng ta coi các hàm trong JavaScript là các giá trị. Hãy xem điều này thông qua một ví dụ:
function calculate(operation) {
switch (operation) {
case "ADD":
return function (a, b) {
console.log(`${a} + ${b} = ${a + b}`);
};
case "SUBTRACT":
return function (a, b) {
console.log(`${a} - ${b} = ${a - b}`);
};
}
}
Trong đoạn mã trên, khi chúng ta gọi hàm calculate
với một đối số, nó sẽ bật đối số đó và cuối cùng trả về một hàm ẩn danh. Vì vậy, nếu chúng ta gọi hàm calculate()
và lưu trữ kết quả của nó trong một biến và console log nó, chúng ta sẽ nhận được kết quả như sau:
const calculateAdd = calculate("ADD");
console.log(calculateAdd);
// Output:
// [Function (anonymous)]
Bạn có thể thấy rằng calculateAdd
chứa một hàm ẩn danh mà calculate()
chức năng trả về.
Có hai cách để gọi hàm bên trong này mà chúng ta sẽ khám phá ngay bây giờ.
Gọi hàm trả về bằng cách sử dụng một biến
Trong phương thức này, chúng ta đã lưu trữ hàm trả về trong một biến như hình trên và sau đó gọi biến đó để lần lượt gọi hàm bên trong.
Hãy xem nó trong mã:
const calculateAdd = calculate("ADD");
calculateAdd(2, 3);
// Output: 2 + 3 = 5
const calculateSubtract = calculate("SUBTRACT");
calculateSubtract(2, 3);
// Output: 2 - 3 = -1
Vậy chúng ta sẽ làm gì ở đây?
- Chúng tôi đã gọi
calculate()
chức năng và thông quaADD
như đối số - Chúng tôi đã lưu trữ hàm ẩn danh được trả về trong
calculateAdd
biến, và - Chúng tôi đã gọi hàm trả về bên trong bằng cách gọi
calculateAdd()
với các đối số cần thiết.
Gọi hàm trả về bằng cách sử dụng dấu ngoặc kép
Đây là một cách rất phức tạp để gọi hàm trả về bên trong. Chúng tôi sử dụng dấu ngoặc kép ()()
trong phương pháp này.
Hãy xem nó trong mã:
calculate("ADD")(2, 3);
// Output: 2 + 3 = 5
calculate("SUBTRACT")(2, 3);
// Output: 2 - 3 = -1
Bạn có thể nghĩ về điều này theo cách tương tự như ví dụ về chuỗi của chúng tôi ở trên. Chỉ là thay vì xâu chuỗi các hàm, chúng ta xâu chuỗi các đối số.
Các đối số trong dấu ngoặc đơn đầu tiên thuộc về hàm bên ngoài, trong khi các đối số trong dấu ngoặc đơn thứ hai thuộc về hàm trả về bên trong.
Các calculate()
phương thức trả về một hàm như đã giải thích trước đó và chính hàm trả về đó được gọi ngay lập tức bằng cách sử dụng dấu ngoặc đơn thứ hai.
Như tôi đã đề cập ở trên, đây là một cách gọi hàm rất phức tạp. Nhưng một khi bạn đã hiểu rõ về nó, nó sẽ trở nên … khá tự nhiên.
Một nơi mà chúng ta có thể thấy loại ký hiệu dấu ngoặc kép này là trong connect
phương pháp trong redux
thư viện quản lý nhà nước. Bạn có thể đọc thêm về connect
đây.
Tóm lược
Trong bài viết này, chúng tôi đã học được:
- Tại sao các chức năng được gọi là công dân hạng nhất trong JS
- chức năng bậc cao hơn là gì
- Cách chuyển một hàm làm đối số cho một hàm khác
- Cách tạo nguyên mẫu mảng, xâu chuỗi hàm, viết polyfill của riêng chúng tôi cho phương thức filter() sẵn có
- Cách trả về một hàm từ một hàm khác và các cách khác nhau để gọi hàm được trả về
Gói (lại
Cảm ơn vì đã đọc! Tôi thực sự hy vọng bạn thấy bài viết về các hàm bậc cao này hữu ích. Hãy theo dõi để biết thêm nội dung tuyệt vời. Hòa bình ra! 🖖
đường liên kết mạng xã hội
