JavaScript 高階函式 – React 白話文運動 04

更新於 發佈於 閱讀時間約 23 分鐘

前言

這一篇會介紹非常重要的 JavaScript 函式概念 – 高階函式(Higher-order function)

高階函數是將一個或多個函數作為參數,或將一個函數作為結果回傳的函數。

在本文中,我們將深入探討什麽是高階函數、使用高階函數的好處以及如何在實際應用中使用高階函數。

  1. 函式導向是什麼?
  2. 純函式(Pure function)
  3. 高階函式(Higher-order function)
  4. 柯理化(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 )指的是函數在執行過程中產生了外部的變化

  1. 使用 Date.now() 或是 Math.random()
  2. 使用 console.log()
  3. 修改外部資料
  4. 操作 DOM
  5. 發起一個 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]


在上面的範例中,arr 是一個包含五個元素的陣列:

我們使用 map 方法對陣列中的每個元素應用一個函式,然後回傳一個包含修改後元素的新陣列。

傳遞給 map 的 call back function 使用箭頭函式,接收一個參數 num。

該函式將 10 加到 num(數組中的每個元素),並回傳結果。

例 2:這里有一個使用者陣列。假設我們只需要使用者的姓名,我們只需使用 map() 方法從 users 陣列中提取即可。

const users = [
{firstName: 'John', lastName: 'Doe', age: 25},
{firstName: 'Jane', lastName: 'Doe', age: 30},
{firstName: 'Jack', lastName: 'Doe', age: 35},
{firstName: 'Jill', lastName: 'Doe', age: 40},
{firstName: 'Joe', lastName: 'Doe', age: 45},
]

const result = users.map((user) => user.firstName + ' ' + user.lastName)
console.log(result); // ['John Doe', 'Jane Doe', 'Jack Doe', 'Jill Doe', 'Joe Doe']


在上述程式碼中,users 是一個代表使用者的物件陣列。每個物件有三個屬性:姓、名、年齡。

我們使用 map() 方法對每個用戶進行映射,以拿到屬性 firstName 和 lastName。

Call back function 接收一個參數 user,它代表 users 陣列(一個對象)中的一個元素。

該函式將使用者的 name 和 lastName 屬性連接起來,並回傳結果。

如何在 JavaScript 中使用 filter()

filter() 函數接收一個陣列並回傳一個新陣列,其中只包含符合特定條件的值。

它也不會改變原始陣列,通常用於根據特定條件從陣列中選擇資料集。

例 1:使用 filter() 函數可以從數字陣列中回傳奇數。

const arr = [1, 2, 3, 4, 5];
const output = arr.filter((num) => num % 2) // filter out odd numbers
console.log(arr); // [1, 2, 3, 4, 5]
console.log(output); // [1, 3, 5]


在上述代碼中,arr 是一個包含五個元素的陣列:

filter 是一種方法,用於創建一個新陣列,其中的元素必須通過所提供的回調函數中指定的測試。

該回調函數通過檢查 num 是否能被 2 整除(num % 2)來檢查 num 是否為奇數。如果 num 不能被 2 整除,函式回傳 true,否則回傳 false。

當在 arr 上使用 filter 時,會對數列中的每個元素使用該函式,創建一個新數列,其中只包含回傳 true 或通過指定條件的元素。原數列保持不變,並回傳結果。

例 2:可以使用 filter() 在數列中只回傳年齡大於 30 歲的用戶。

const users = [
{firstName: 'John', lastName: 'Doe', age: 25},
{firstName: 'Jane', lastName: 'Doe', age: 30},
{firstName: 'Jack', lastName: 'Doe', age: 35},
{firstName: 'Jill', lastName: 'Doe', age: 40},
{firstName: 'Joe', lastName: 'Doe', age: 45},
]

// Find the users with age greater than 30
const output = users.filter(({age}) => age > 30)
console.log(output); // [{firstName: 'Jack', lastName: 'Doe', age: 35}, {firstName: 'Jill', lastName: 'Doe', age: 40}, {firstName: 'Joe', lastName: 'Doe', age: 45}]


在上述程式碼中,users 是一個代表用戶的陣列。每個物件有三個屬性:名、姓和年齡。

在 users 數組上使用 filter,並對陣列中的每個元素使用回調函數。

函式接收一個參數,換句話說,一個被重組為單個屬性 age 的對象。

該函式檢查年齡是否大於 30 歲。如果是的話,函數回傳 true,否則回傳 false。

在對 users 使用 filter 時,它會對陣列中的每個元素使用此函式,創建一個新陣列,其中只包含傳給函式時回傳 true 的元素,並回傳結果。原始的 users 陣列保持不變。

如何在 JavaScript 中使用 reduce()

reduce() 在滿多人心中,是相對複雜的函式,如果以前接觸過 reduce() 方法,但一開始無法理界,那麼就繼續看下去吧!

我們可能會有一個疑問:為什麽要使用 reduce() ?因為已經有很多好用的函式了,我們如何決定使用哪一個,以及何時使用?

就 reduce() 而言,當你想對陣列元素執行某種操作並回傳一個單一值時,就應該使用它。

單一值 :指的是對陣列元素重覆應用函數後的累積結果。

例如,您可以使用 reduce() 求數列中所有元素的總和、查找最大值或最小值、將多個對象合併成一個對象,或對數列中的不同元素進行分割。我們來透過範例了解這些。

例 1:使用 reduce() 求數組中所有元素的和:

const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((total, currentValue) => {
return total + currentValue;
}, 0)

console.log(sum); // 15


在這個範例中,reduce() 方法在數列中使用,並通過一個回調函數,該函數有兩個參數:total 和 currentValue。

total 參數是使用函數時,回傳值的累加,而 currentValue 是數列中正在處理的當前元素。

reduce() 的第二個參數也是初始值,在以上範例中為 0,用作第一次疊代時 total 的初始值。

在每次疊代中,函數都會將當前值與總數相加,並回傳總數的新值。

接下來,reduce() 方法使用回傳值作為下一次疊代的總數,直到處理完數列中的所有元素。

最後,它會回傳總數的最終值,即數列中所有元素的總和。

例 2:使用 reduce() 求數列中的最大值:

let numbers = [5, 20, 100, 60, 1];
const maxValue = numbers.reduce((max, curr) => {
if(curr > max) max = curr;
return max;
});
console.log(maxValue); // 100


在此範例中,我們再次在回調函數中使用了 max 和 curr 這兩個參數,這次我們沒有在 reduce() 方法中傳遞第二個參數。因此,默認值將是數列中的第一個元素。

回調函數首先檢查當前元素 curr 是否大於當前最大值 max。如果是,它就會更新 max 的值,使其成為當前元素。如果不是,則不更新 max。最後,函式回傳 max 的值。

在本例中,reduce() 方法首先會將 max 設置為 5,將 curr 設置為 20。然後,它會檢查 20 是否大於 5,結果是大於 5,因此會將 max 更新為 20。

然後將 curr 設置為 100,並檢查 100 是否大於 20,結果是大於 20,因此將 max 更新為 100。

這個過程一直持續到處理完數組中的所有元素為止。max 的最終值將是數列中的最大值,在此範例中就是 100。

高階函數的優點

使用高階函數對開發者有一些重要的好處。

首先,高階函數可以使程式碼更加簡潔易懂,有助於提高程式碼的可讀性,有助於加快開發過程,並使程式碼使用更加容易。

其次,高階函數可以幫助將程式碼吃組話,使其更易於維護和擴展。

柯理化(Currying)

在高階函式中,柯理化(Currying)是一個特殊且重要的技巧。

可以分別將一個高階函式中,不同階層的引數傳入,也可以透過此概念,為一個高階函數新增前綴(prefix)

const userLogs = userName => message => console.log(`${userName} -> ${message}`)

const log1 = userLogs("Hogan");
log1("Hello World");
log1("Hello");

const log2 = userLogs("Bobo");
log2("Hello World");
log2("Hello");


這邊也使用一個範例來實作,其中也有給了兩個不同的高階函式的前綴(prefix)

結語

本文探討了函式導向的概念、什麽是高階函數、使用高階函數的好處以及如何在實際應用中使用高階函數,另外也用了比較小的篇幅介紹了柯理化(Curring)是一個什麼樣的功能,也針對純函數(Pure Function)做了一個介紹。

通過使用高階函數,開發者可以更聰明地寫程式,將程式碼模組化,使程式碼更清晰、更易於使用。

如果有任何建議與疑問也歡迎留言!

如果喜歡此系列文章,請不吝於按下喜歡及分享,讓更多人看到唷~

其他文章參考

JavaScript Async Await – React 白話文運動 03

JavaScript ES6 Object – React 白話文運動 02

JavaScript ES6 – React 白話文運動 01

Zustand 是什麼? React 前端狀態管理


希望能透過「React框架白話文運動」系列文章,利用口語化語表以及簡單的程式碼範例,能讓讀者更明白React的各種應用。 系列文章會講述以下: 1. 了解 ES6 JavaScript 語法 2. 了解 React 的運作原理 3. 了解 React 的狀態管理 4. 使用 React Hook管理狀態並且存取資料
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
歡迎來到 React 白話文運動第三篇,今天我們將深入探討 JavaScript 中的非同步操作,並介紹兩個重要的關鍵字:Async 與 Await。在這篇文章中,我們將透過實例演示非同步操作的概念,以及如何使用 Promise、Fetch、Async 和 Await 來更有效地處理非同步程式。
今天會接續介紹 JS ES6 其他新穎的語法,物件解構(Object Destructuring)物件語法強化(Object Literal Enhancement)陣列解構(Array Destructuring)延展運算子(Spread Operator)。
在進入到 React 前端框架之前,幫讀者複習有關 JavaScript 的知識。此篇著重,JavaScript 的歷史、let & const & var 差異、樣板字面值( Template Literals )、箭頭函式( Arrow Function )JS ES6 一個非常重要的前置知識。
歡迎來到 React 白話文運動第三篇,今天我們將深入探討 JavaScript 中的非同步操作,並介紹兩個重要的關鍵字:Async 與 Await。在這篇文章中,我們將透過實例演示非同步操作的概念,以及如何使用 Promise、Fetch、Async 和 Await 來更有效地處理非同步程式。
今天會接續介紹 JS ES6 其他新穎的語法,物件解構(Object Destructuring)物件語法強化(Object Literal Enhancement)陣列解構(Array Destructuring)延展運算子(Spread Operator)。
在進入到 React 前端框架之前,幫讀者複習有關 JavaScript 的知識。此篇著重,JavaScript 的歷史、let & const & var 差異、樣板字面值( Template Literals )、箭頭函式( Arrow Function )JS ES6 一個非常重要的前置知識。
你可能也想看
Google News 追蹤
Thumbnail
現代社會跟以前不同了,人人都有一支手機,只要打開就可以獲得各種資訊。過去想要辦卡或是開戶就要跑一趟銀行,然而如今科技快速發展之下,金融App無聲無息地進到你生活中。但同樣的,每一家銀行都有自己的App時,我們又該如何選擇呢?(本文係由國泰世華銀行邀約) 今天我會用不同角度帶大家看這款國泰世華CUB
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
函式(Function)是 JavaScript 中用來完成特定任務的可重複執行的程式碼片段。 函式可以接受輸入(參數),進行處理,並回傳結果。 主要的函式建立方式有函式宣告、函式表達式、和箭頭函式。
Thumbnail
Higher-Order Function(高階方程式) 是什麼 Higher-Order Function簡稱HOF,是指一個以function作為參數的function或者回傳function的function,我知道目前聽起來非常抽象,我們舉一個我們之前就已經看過的例子。 以Funct
主要來講宣告函式跟箭頭函式 : 宣告函式(Function Declaration) 語法: function functionName(parameters) { return result; } 特點: 使用 function 關鍵字 函式名稱是必需的 存在函式
Thumbnail
在這一章中,我們探討了 PHP 中的函數,包括函數的基本結構、不同的函數定義方式(如函數聲明、函數表達式、箭頭函數和匿名函數)以及如何呼叫函數。我們還討論了函數的參數處理方式,包括單個參數、多個參數、預設參數值和剩餘參數。此外,我們還介紹了函數的返回值,包括返回單個值、返回物件和返回函數的情況。
Thumbnail
本章節主要介紹Java語言中的函數(也稱為方法)的使用,包括函數的基本結構、函數表達式(Lambda表達式)、箭頭函數、匿名函數的使用,以及如何呼叫函數、如何使用函數參數和函數的返回值等內容。通過學習本章節,讀者將能夠熟練掌握Java語言中的函數相關知識,並能夠在實際編程中靈活運用。
Thumbnail
這章節的目的是介紹 Kotlin 語言中函數的基本用法和概念,包括函數的聲明、使用、參數和返回值等。通過學習這章節,讀者可以熟練掌握如何在 Kotlin 中定義和使用函數,來解決各種問題。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹 C# 中函數的基本結構,包括訪問修飾符、返回類型、方法名稱、參數列表和方法體。同時,也介紹了函數的各種呼叫方式、參數傳遞方式和返回值類型。讀者可以通過本章節,深入理解 C# 中函數的使用和應用。
※ 函式進階介紹: 箭頭函式: 箭頭函式相比於一般函式,語法相當簡潔。除了少去 function 關鍵字,如果只有一個參數,箭頭函式可以省略括號;只有一行程式碼,就是直接簡單返回一個變數或簡單的表達式,可以省略大括號和 return。例子如下: //一般函式計算平方用的寫法 const squ
※ 函式基礎介紹: ※ JavaScript 特殊的函式特性: 函式可以當成值來傳遞 (可以放進變數或放進物件) 函式可以當成函式的參數 callback - 在特定事件中觸發函式 (非同步特性) ※ 函式的基本寫法: ※ 調用 (invoke) 函式: "調用" 意指呼叫或執行
Thumbnail
現代社會跟以前不同了,人人都有一支手機,只要打開就可以獲得各種資訊。過去想要辦卡或是開戶就要跑一趟銀行,然而如今科技快速發展之下,金融App無聲無息地進到你生活中。但同樣的,每一家銀行都有自己的App時,我們又該如何選擇呢?(本文係由國泰世華銀行邀約) 今天我會用不同角度帶大家看這款國泰世華CUB
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
函式(Function)是 JavaScript 中用來完成特定任務的可重複執行的程式碼片段。 函式可以接受輸入(參數),進行處理,並回傳結果。 主要的函式建立方式有函式宣告、函式表達式、和箭頭函式。
Thumbnail
Higher-Order Function(高階方程式) 是什麼 Higher-Order Function簡稱HOF,是指一個以function作為參數的function或者回傳function的function,我知道目前聽起來非常抽象,我們舉一個我們之前就已經看過的例子。 以Funct
主要來講宣告函式跟箭頭函式 : 宣告函式(Function Declaration) 語法: function functionName(parameters) { return result; } 特點: 使用 function 關鍵字 函式名稱是必需的 存在函式
Thumbnail
在這一章中,我們探討了 PHP 中的函數,包括函數的基本結構、不同的函數定義方式(如函數聲明、函數表達式、箭頭函數和匿名函數)以及如何呼叫函數。我們還討論了函數的參數處理方式,包括單個參數、多個參數、預設參數值和剩餘參數。此外,我們還介紹了函數的返回值,包括返回單個值、返回物件和返回函數的情況。
Thumbnail
本章節主要介紹Java語言中的函數(也稱為方法)的使用,包括函數的基本結構、函數表達式(Lambda表達式)、箭頭函數、匿名函數的使用,以及如何呼叫函數、如何使用函數參數和函數的返回值等內容。通過學習本章節,讀者將能夠熟練掌握Java語言中的函數相關知識,並能夠在實際編程中靈活運用。
Thumbnail
這章節的目的是介紹 Kotlin 語言中函數的基本用法和概念,包括函數的聲明、使用、參數和返回值等。通過學習這章節,讀者可以熟練掌握如何在 Kotlin 中定義和使用函數,來解決各種問題。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹 C# 中函數的基本結構,包括訪問修飾符、返回類型、方法名稱、參數列表和方法體。同時,也介紹了函數的各種呼叫方式、參數傳遞方式和返回值類型。讀者可以通過本章節,深入理解 C# 中函數的使用和應用。
※ 函式進階介紹: 箭頭函式: 箭頭函式相比於一般函式,語法相當簡潔。除了少去 function 關鍵字,如果只有一個參數,箭頭函式可以省略括號;只有一行程式碼,就是直接簡單返回一個變數或簡單的表達式,可以省略大括號和 return。例子如下: //一般函式計算平方用的寫法 const squ
※ 函式基礎介紹: ※ JavaScript 特殊的函式特性: 函式可以當成值來傳遞 (可以放進變數或放進物件) 函式可以當成函式的參數 callback - 在特定事件中觸發函式 (非同步特性) ※ 函式的基本寫法: ※ 調用 (invoke) 函式: "調用" 意指呼叫或執行