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

Javascript 高階函式 -HigherOrderFunction

目錄

前言

這一篇會介紹非常重要的 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]