JavaScript進階之路系列(一): 高階函數

看了這篇文章,你就會高階函數了,是不是聽起來很牛?高階函數,聽起來很高級,其實是很接地氣,大家經常會用到的東西,比如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也是一個典型的高階函數。

總結:

高階函數就只講這兩個方法了,我相信以各位的聰明才智已經理解了什麼是高階函數。

最後留一個小思考,閉包,是不是高階函數呢?