JavaScript進階之路系列(一): 高階函數
- 2020 年 4 月 9 日
- 筆記
看了這篇文章,你就會高階函數了,是不是聽起來很牛?高階函數,聽起來很高級,其實是很接地氣,大家經常會用到的東西,比如filter,map,回調函數。
高階函數是對其他函數進行操作的函數,可以將它們作為參數或通過返回它們。簡單來說,高階函數是一個函數,它接收函數作為參數或將函數作為輸出返回。
在《javascript設計模式和開發實踐》中是這樣定義的: 1.函數可以作為參數被傳遞; 2.函數可以作為返回值輸出。
舉個例子:
function foo(f){ if(f instanceof Function){ f(); } } foo(function(){ alert("asdf"); })
定義了一個foo函數,foo函數傳入了一個函數作為參數,foo函數裡面添加判斷,參數如果是函數就執行該參數。這就是一個簡單的高階函數。
回調函數(callback)
這個基本上是我們非常常用的高階函數了,我們可以來看一下回調函數的程式碼:
function greeting(name) { alert('Hello ' + name); } function processUserInput(callback) { var name = prompt('請輸入你的名字。'); callback(name); } processUserInput(greeting);
定義了一個greeting函數,又定義了一個processUserInput函數,之後我們又把greeting函數作為參數傳到了processUserInput中,這個就符合了高階函數中的第一個定義,作為參數被傳遞。
回調函數作為高階函數中的一種,它是幹什麼的呢?
回調函數是指 使用者自己定義一個函數,實現這個函數的程式內容,然後把這個函數(入口地址)作為參數傳入別人(或系統)的函數中,由別人(或系統)的函數在運行時來調用的函數。
這麼說,可能聽不明白。
callback 一詞本來用於打電話。你可以打電話(call)給別人,也可以留下電話號碼,讓別人回電話(callback)。回調函數的意思就是,你寫了一個函數,讓別人來調用,就是回調函數。

來看一看非同步的回調函數:
function ff(a, b, cbk) { setTimeout(() => { cbk(a + b); }, 3000); } function f(callback) { var x = 3, y = 4; var z = 0; callback(x, y, function (re) { z = re; console.log(z) }); console.log("主函數") return z; } f(ff);
對比來看,回調與同步、非同步並沒有直接的聯繫,回調只是一種實現方式,既可以有同步回調,也可以有非同步回調,還可以有事件處理回調和延遲函數回調,這些在我們工作中有很多的使用場景。
我們可以像使用變數一樣使用函數,作為另一個函數的參數,在另一個函數中作為返回結果,在另一個函數中調用它。當我們作為參數傳遞一個回調函數給另一個函數時,我們只傳遞了這個函數的定義,並沒有在參數中執行它。
當包含(調用)函數擁有了在參數中定義的回調函數後,它可以在任何時候調用(也就是回調)它。
這說明回調函數並不是立即執行,而是在包含函數的函數體內指定的位置「回調」它(形如其名)。
回調函數是閉包的。
當作為參數傳遞一個回調函數給另一個函數時,回調函數將在包含函數函數體內的某個位置被執行,就像回調函數在包含函數的函數體內定義一樣。閉包函數可以訪問包含函數的作用域,所以,回調函數可以訪問包含函數的變數,甚至是全局變數。
什麼時候用回調函數?
我一般和別人合作項目的時候,想讓人感覺我的程式碼寫的很厲害,有時候會故意寫兩個回調,但是這種行為不可取的,減少程式碼冗餘,無用的程式碼只會造成維護上的困難。
假如,你點擊了一個按鈕,你想讓它執行連個函數,先執行函數A,再執行函數B,這時候就可以用回調函數了。
//定義主函數,回調函數作為參數 function A(callback) { callback(); console.log('我是主函數'); } //定義回調函數 function B(){ setTimeout("console.log('我是回調函數')", 3000);//模仿耗時操作 } //調用主函數,將函數B傳進去 A(B);
這裡有一個問題,我們執行A函數後,執行B函數,為什麼不直接在A函數里調用,要傳參過去呢?
function A(callback) { B(); console.log('我是主函數'); } function B(){ setTimeout("console.log('我是回調函數')", 3000);//模仿耗時操作 }
這段程式碼的作用和回調函數好像是一樣的。其實這兩種方法在性能上是沒有區別的,只是在靈活性上有很大的區別。 例如,我定義了一個C函數為回調函數。
//定義主函數,回調函數作為參數 function A(callback) { callback(); console.log('我是主函數'); } //定義回調函數 function B(){ setTimeout("console.log('我是回調函數')", 3000);//模仿耗時操作 } //調用主函數,將函數B傳進去 function C(){ setTimeout("console.log('我是回調函數')", 3000);//模仿耗時操作 } //調用主函數,將函數C傳進去 A(B); A(C);
這時候,在試一下這段程式碼,就會出現很大的分歧。
function A(callback) { B(); C(); console.log('我是主函數'); } function B(){ setTimeout("console.log('我是回調函數')", 3000);//模仿耗時操作 } function C(){ setTimeout("console.log('我是回調函數')", 3000);//模仿耗時操作 }
過濾器(filter)
這是另一種高階函數,老樣子,先看一下程式碼。
array.filter(function(currentValue,index,arr), thisValue)
filter 接收一個函數作為參數,所以它也符合高階函數中的第一個定義,作為參數被傳遞。
那什麼是filter呢? filter()方法會創建一個新數組,原數組的每個元素傳入回調函數中,回調函數中有return返回值,若返回值為true,這個元素保存到新數組中;若返回值為false,則該元素不保存到新數組中;原數組不發生改變。
簡單來說,filter就是一個過濾數組的方法,符合條件的被傳入新的數組,不符合條件的,就不管它了。
舉個例子:
在一個Array中,刪掉偶數,只保留奇數,可以這麼寫:
var arr = [1, 2, 4, 5, 6, 9, 10, 15]; var r = arr.filter(function (x) { return x % 2 !== 0; }); r; // [1, 5, 9, 15]
定義了arr這個數組,arr數組使用的過濾器,過濾器中函數的作用就是把數組中的偶數過濾出來,放進r數組中。
使用filter,注意兩點,1.filter() 不會對空數組進行檢測;2. filter() 不會改變原始數組。
過濾器很強大,比如,我們面試經常會考的數組去重:
'use strict'; var r,arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry']; r = arr.filter(function (element, index, self) { return self.indexOf(element) === index; }); console.log(r.toString()); //apple,strawberry,banana,pear,orange
map
map()方法定義在JavaScript的Array中,它返回一個新的數組,數組中的元素為原始數組調用函數處理後的值。
注意: 1.map()不會對空數組進行檢測;2.map()不會改變原始數組。
來看一看map的示例:
function pow(x){ return x*x; } var arr = [1,2,3,4,5,6]; var results = arr.map(pow); console.log(results); //[1,4,9,16,25,36]
定義了一個pow函數,函數的做用是讓參數平方,map函數的參數是pow參數,map遍歷了數組,把數組的每一項傳進了pow函數裡面,再return出來。
map也是一個典型的高階函數。
總結:
高階函數就只講這兩個方法了,我相信以各位的聰明才智已經理解了什麼是高階函數。
最後留一個小思考,閉包,是不是高階函數呢?