天天看點

如何在JavaScript中使用高階函數

将另一個函數作為參數的函數,或者定義一個函數作為傳回值的函數,被稱為高階函數。

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的便利性。高階函數允許我們建立自定義命名的函數,用一階函數的共享模闆代碼執行專門的任務。

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

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

總結

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

繼續閱讀