目錄
前言
這一篇會介紹非常重要的 JavaScript 函式概念 – 高階函式(Higher-order function)
高階函數是將一個或多個函數作為參數,或將一個函數作為結果回傳的函數。
在本文中,我們將深入探討什麽是高階函數、使用高階函數的好處以及如何在實際應用中使用高階函數。
- 函式導向是什麼?
- 純函式(Pure function)
- 高階函式(Higher-order function)
- 柯理化(Currying)
函式導向是什麼?
所謂的函式導向,一言以蔽之,函式本身就是一個變數。舉例來說:函數本身可以使用const、let、var去做宣告,並且可以變成一個引數(Argument)傳入其他的函式裡面,也可以將他加進陣列、物件裡面。
將函數變成一個引數傳到其他函數
const print = (message) => {
console.log(`print function with ${message}`)
}
const helloMessage = () => {
return "Hello Message"
}
print(helloMessage());
// print function with Hello Message
加入陣列
const array = ["item0", (message) => console.log("I'm item function in array " + message)]
console.log(array[0]);
// item0
array[1]("argument");
// I'm item function in array argument
加入物件
const object = {
helloWorld: "Hello World",
print: (message) => {
console.log(`print function with ${message}`)
}
}
object.print(object.helloWorld);
// print function with Hello World
純函式( Pure function )
當一個函數只受引數( Argument )的影響時,我們稱為純函數( Pure function )。這樣的純函數,因為不會受到其他干擾,這樣的函數具有封裝性,不會受到其他變數以及引數的干擾,也就是會有副作用( Side Effect )。
所謂的副作用( Side Effect )指的是函數在執行過程中產生了外部的變化
- 使用 Date.now() 或是 Math.random()
- 使用 console.log()
- 修改外部資料
- 操作 DOM
- 發起一個 HTTP Request
來看以下的例子,這個例子為一個非純函式,且當修改外部的資料,則函式會受影響
let y = 1;
function xAdd(x) {
return x + y;
};
xAdd(5); //6
可以看到即便是執行了 xAdd(5) 也會因為y的改變,導致執行結果不同。
因此應該將函式變成一個有封裝性,不受外部影響的純函式,純函式的好處除了擁有獨立性以外,也可以更容易寫測試。
function sum(x, y) {
return x + y;
};
sum(1,2); //3
高階函式(Higher-order function)
高階函式(Higher-order function)是指「接受或是回傳函式」的函式。
所謂的接受是指將函式作為引數(Argument)進入一個函式。
回傳函式(Call back function)則是將一個函式作為變數值回傳,有幾種不同類型的高階函數,如 map 和 reduce。
上面的函數導向介紹,就是其中一個例子,讓我們在複習一下。
// Callback function, passed as a parameter in the higher order function
function callbackFunction(){
console.log('I am a callback function');
}
// higher order function
function higherOrderFunction(func){
console.log('I am higher order function')
func()
}
higherOrderFunction(callbackFunction);
在上述程式碼中,higherOrderFunction() 是一個 HOF,因為我們將一個回調函數作為參數傳遞給它。
上面的範例很簡單,讓我們進一步展開,看看如何使用 HOF 編寫更簡潔、更模塊化的代碼。
高階函數的工作原理
假設要寫一個計算圓的面積和直徑的函數。作為剛學程式的人,我們首先想到的解決方案是分別編寫計算面積或直徑的函數。
const radius = [1, 2, 3];
// function to calculate area of the circle
const calculateArea = function (radius) {
const output = [];
for(let i = 0; i< radius.length; i++){
output.push(Math.PI * radius[i] * radius[i]);
}
return output;
}
// function to calculate diameter of the circle
const calculateDiameter = function (radius) {
const output = [];
for(let i = 0; i< radius.length; i++){
output.push(2 * radius[i]);
}
return output;
}
console.log(calculateArea(radius));
console.log(calculateDiameter(radius))
但你注意到上述程式碼的問題嗎?
我們正在重覆寫幾乎相同的函式,但邏輯卻略有不同,而且,我們寫的函式也不能重覆使用,那麽,讓我們看看如何使用高階函數來寫相同的程式碼:
const radius = [1, 2, 3];
// logic to clculate area
const area = function(radius){
return Math.PI * radius * radius;
}
// logic to calculate diameter
const diameter = function(radius){
return 2 * radius;
}
// a reusable function to calculate area, diameter, etc
const calculate = function(radius, logic){
const output = [];
for(let i = 0; i < radius.length; i++){
output.push(logic(radius[i]))
}
return output;
}
console.log(calculate(radius, area));
console.log(calculate(radius, diameter));
正如在上述程式碼中看到的,我們只寫了一個函式 calculate() 來計算圓的面積和直徑。我們只需寫邏輯並將其傳遞給 calculate(),函數就會完成工作。
我們使用高階函式寫的程式碼既簡潔又模組化,每個函式各司其職,我們在這里沒有重覆任何事情。
假設將來我們要寫一個計算圓周長的程式。我們只需寫計算圓周率的邏輯,並將其傳遞給 calculate() 函式即可。
const circumeference = function(radius){
return 2 * Math.PI * radius;
}
console.log(calculate(radius, circumeference));
這裡也提供其他箭頭函式的反例與應用。
const print = (message) => {
console.log(`print function with ${message}`)
}
const helloMessage = () => {
return "Hello Message"
}
print(helloMessage());
// print function with Hello Message
不過高階函式可以讓我們處理更多方便且複雜的情況。
const printNameByCondition = (condition, trueFunc, falseFunc) => {
condition ? trueFunc() : falseFunc();
}
const printHogan = () => console.log("Hello Hogan");
const printBobo = () => console.log("Hello BoBo");
printNameByCondition(true, printHogan, printBobo);
// Hello Hogan
printNameByCondition(false, printHogan, printBobo);
// Hello BoBo
這邊可以看到,我建立了一個函數,裡面有三個引數(Argument),其中後兩者為函數。
透過第一個引述,來去做判斷,如果是true的情況,執行第一個函數,否則就是執行第二個函數。
如何使用高階函數
我們可以使用多種方式來時做高階函數,在處理 array 時,可以使用 map()、reduce()、filter() 和 sort() 函式來處理和轉換陣列中的資料。
高階函數處理物件
可以使用 Object.entries() 函數從對象創建一個新物件。
高階函數使用函式
可以使用 compose() 函式從較簡單的函式創建覆雜的函式。
如何使用一些重要的高階函數
JavaScript 內建的的高階函數有很多,其中最常見的有 map()、filter() 和 reduce()。下面我們就來詳細了解一下它們。
如何在 JavaScript 中使用 map()
map() 函式接收一個陣列,並對陣列中的每個值進行轉換,且不會改變原始陣列。通常用於將數值陣列轉換為具有不同結構的新陣列。
例 1:假設我們要給陣列中的每個元素都加上 10。我們可以使用 map() 方法 reflect 數組中的每個元素,將其加上 10。
const arr = [1, 2, 3, 4, 5];
const output = arr.map((num) => num += 10)
console.log(arr); // [1, 2, 3, 4, 5]
console.log(output); // [11, 12, 13, 14, 15]