如何在JavaScript中使用高階函數

2022-09-17 12:01:20

將另一個函數作為引數的函數,或者定義一個函數作為返回值的函數,被稱為高階函數。

JavaScript可以接受高階函數。這種處理高階函數的能力以及其他特點,使JavaScript成為非常適合函數語言程式設計的程式語言之一。

JavaScript將函數視為一等公民

你也許聽說過,JavaScript函數是一等公民。這意味著,在JavaScript中函數是物件。

它們的型別是Object,它們可以作為一個變數的值被分配,而且它們可以像其他參照變數一樣被傳遞和返回。

一等函數賦予了JavaScript特殊的能力,使我們能夠從高階函數中獲益。

由於函數是物件,且JavaScript是流行的程式語言之一,因此其支援函數語言程式設計的原生方法。

事實上,一等函數是JavaScript的原生方法。我敢打賭你在使用他們的時候甚至都沒有想過正在使用函數。

高階函數接收函數作為引數

如果你做過很多JavaScript開發,你可能遇到過使用回撥函數的情況。

回撥函數是一個在操作結束時執行的函數,一旦所有其他操作完成後便會執行。

通常情況下,我們把這個函數作為最後的引數傳遞,在其他引數之後。它通常被定義為內聯的匿名函數。回撥函數依靠的是JavaScript處理高階函數的能力。

JavaScript是一個單執行緒語言。這意味著同一時間只有一個操作會被執行。

為了避免操作或系統的主執行緒互相阻塞(這將導致死鎖),引擎會確保所有操作按順序執行。它們沿著這個單執行緒排隊,直到安全產生另一個程式碼事務。

將一個函數作為引數傳入,並在父函數的其他操作完成後執行該函數的能力,對於支援高階函數的語言來說是至關重要的。

JavaScript中的回撥函數允許非同步行為,因此指令碼可以在等待結果的同時繼續執行其他函數或操作。

在處理可能在不確定的時間段後返回結果的資源時,傳遞迴撥函數的能力至關重要。

這種高階函數模式在網路開發中非常有用。一個指令碼可以向伺服器傳送一個請求,然後需要在響應到來時進行處理,而不需要了解伺服器的網路延遲或處理時間。

Node.js經常使用回撥函數來有效地利用伺服器資源。這種非同步方法對於等待使用者輸入後再執行函數的應用程式來說也很有用。

考慮一下這個簡單的JavaScript片段,它為一個按鈕新增了一個事件監聽器。

document.getElementById("clicker").addEventListener("click", function() {
alert("you triggered " + this.id);
});

這段指令碼使用內聯匿名函數來顯示一個alert

但它也可以很容易地使用一個單獨定義的函數,並將這個命名函數傳遞給addEventListener方法。

var proveIt = function() {
alert("you triggered " + this.id);
};

document.getElementById("clicker").addEventListener("click", proveIt);

我們這樣做不僅僅是展示了高階函數。我們使程式碼更可讀,更有彈性,併為不同的任務分離了功能(監聽點選事件與提醒使用者)。

程式碼可重用性

我們的proveIt()函數在結構上獨立於它周圍的程式碼,總是返回被觸發的元素的id。這種函數設計的方法是函數語言程式設計的核心。

這段程式碼可以存在於任何你用元素的id顯示alert的上下文中,並且可以被任何事件監聽器呼叫。

用一個單獨定義和命名的函數取代行內函式的能力為我們提供了無限可能。

在函數語言程式設計中,我們試圖開發不改變外部資料的純函數,並且每次對相同的輸入返回相同的結果。

現在我們有了一個基本的工具,可以幫助我們開發一個小型的、有針對性的高階函數庫,你可以在任何應用程式中使用。

請注意,我們把 proveIt 而不是 proveIt() 傳遞給我們的 addEventListener 函數。

  • 當你不帶括號傳遞一個函數的名字時,你傳遞的是函數物件本身。
  • 當你用圓括號傳遞函數時,你是在傳遞執行該函數的結果。

返回函數

除了將函數作為引數之外,JavaScript還允許函數將其他函數作為結果返回。

這是說得通的,因為函數是簡單的物件。物件(包括函數)可以被定義為一個函數的返回值,就像字串、陣列或其他值。

但是函數作為結果返回是什麼意思呢?

函數是分解問題和建立可重用程式碼片斷的一種強大方式。當我們將一個函數定義為一個高階函數的返回值時,它可以作為新函數的模板。

假如你讀了太多關於"千禧一代"的文章,感到厭煩。你決定每當出現"千禧一代"這個詞時,你都要用 "蛇人"這個短語來代替它。

你可能是簡單地寫一個函數,在你傳遞給它的任何文字上執行該文字替換。

var snakify = function(text) {
return text.replace(/millenials/ig, "Snake People");
};
console.log(snakify("The Millenials are always up to something."));
// The Snake People are always up to something.

這種寫法是有效的,但不夠通用。你可能想為其他情況寫一個替換函數:

var hippify = function(text) {
return text.replace(/baby boomers/ig, "Aging Hippies");
};
console.log(hippify("The Baby Boomers just look the other way."));
// The Aging Hippies just look the other way.

但是,如果你決定要做一些更復雜的事情來保留原始字串中的大小寫呢?你將不得不修改你的兩個新函數來做到這一點。

這很麻煩,而且會使你的程式碼更加脆弱,也更難閱讀。在這樣的情況下,我們可以使用高階函數作為解決方案。

高階函數模板

你真正想要的是能夠在模板函數中用任何其他術語替換任何術語的靈活性,並將該行為定義為一個基礎函數,你可以在此基礎上建立新的自定義函數。

有了將函數指定為返回值的能力,JavaScript提供了讓這種情況更便捷的方法:

var attitude = function(original, replacement, source) {
	return function(source) {
		return source.replace(original, replacement);
	};
};

var snakify = attitude(/millenials/ig, "Snake People");
var hippify = attitude(/baby boomers/ig, "Aging Hippies");

console.log(snakify("The Millenials are always up to something."));
// The Snake People are always up to something.
console.log(hippify("The Baby Boomers just look the other way."));
	// The Aging Hippies just look the other way.

我們所做的是把做實際工作的程式碼隔離到一個通用的、可延伸的attitude函數中。它封裝了所有需要修改任何輸入字串的工作:使用原始短語作為初始值,並輸出一個具有某種態度的替換短語。

當我們將這個新函數定義為對attitude高階函數的參照,並預先填入它所接收的前兩個引數時,我們會得到什麼?它允許新函數接收你傳遞給它的任何文字,並在我們定義的返回函數中使用該引數作為attitude函數的輸出。

JavaScript函數不關心傳遞給它們的引數的數量。

如果缺少第二個引數,函數將把它視為undefined。當我們選擇不提供第三個引數,或任何數量的額外引數時,它也會這樣做。

此外,你可以在以後再傳入那個額外的引數。你可以在定義了你想呼叫的高階函數後這樣做,就像剛才演示的那樣。

我們正在建立一個模板高階函數來返回另一個函數。然後,我們把這個新返回的函數,除去一個屬性,定義為模板函數的一個自定義實現。

你以這種方式建立的所有函數將繼承高階函數的工作程式碼。然而,你可以用不同的預設引數預先定義它們。

正在使用高階函數

高階函數對於JavaScript的工作方式來說是起碼的,你已經在使用它們了。

每當你傳遞一個匿名函數或回撥函數時,你實際上是把所傳遞的函數返回的值,作為另一個函數的引數(如箭頭函數)使用。

開發人員在學習JavaScript的早期就熟悉高階函數。它是JavaScript設計中固有的,所以以後才需要學習驅動箭頭函數或回撥的概念。

為返回其他函數的函數賦值的能力擴充套件了JavaScript的便利性。高階函數允許我們建立自定義命名的函數,用一階函數的共用模板程式碼執行專門的任務。

這些函數中的每一個都可以繼承高階函數中的任何改進。這可以協助我們避免程式碼重複,並保持我們的原始碼的整潔和可讀性。

如果你確保你的函數是純淨的(它們不改變外部值,並且對於任何給定的輸入總是返回相同的值),你可以建立測試來驗證當你更新一階函數時,你的程式碼變化不會破壞任何東西。

總結

現在你知道了高階函數的工作原理,你可以開始考慮如何在自己的專案中利用這個概念了。

JavaScript的一個好處是,你可以將函數技術與你已經熟悉的程式碼混合在一起。

即便你一開始只是為了使用高階函數而使用,你也會很快熟悉它們所提供的額外靈活性。

現在使用高階函數的一點工作可以在未來幾年內改善你的程式碼。

以上就是本文的全部內容,感謝閱讀~