原文連接: Understanding Higher-Order Functions in JavaScript
原文做者: Sukhjinder Arora
譯者: 進擊的大蔥
推薦理由: 本文詳細介紹了什麼是函數式編程以及如何寫本身的高階函數High-order functions。javascript
若是你有學習過JavaScript, 你必定有聽太高階函數這個詞。高階函數雖然聽起來比較複雜,但是實際上並不難。java
JavaScript之因此很適合拿來進行函數式編程是由於它容許高階函數的存在。編程
爲了徹底理解高階函數的概念,你必定要先理解什麼是函數式編程(Functional Programming)以及一等類函數(First-class Functions)的概念。數組
用最簡單的話來講,函數式編程就是將函數做爲另一個函數的參數或者返回值。在函數式編程的世界裏面,咱們用函數的方式進行思考和編碼。bash
JavaScript,HasKell,Clojure,Scala和Erlang是一些支持函數式編程的語言。函數式編程
若是你使用過JavaScript,你或許據說過JavaScript將函數做爲一等公民進行對待。這是由於在JavaScript或者其餘語言裏面,函數也是對象。函數
在JavaScript中函數是一種特殊類型的對象。它們是Function
對象。舉個例子:學習
function greeting() {
console.log('Hello World');
}
// 調用函數
greeting(); // 打印 'Hello World'
複製代碼
爲了驗證JavaScript的函數也是對象,咱們能夠對函數進行像對象同樣的賦值操做:測試
greeting.lang = 'English';
// 輸出 'English'
console.log(greeting.lang);
複製代碼
備註 - 雖然這樣寫沒有任何錯,不過給函數對象賦值是一個十分危險的作法。你不該該隨便爲函數對象添加任意的屬性,若是你有這個需求請使用object。ui
在JavaScript裏面,全部你能夠對其餘類型例如對象,字符串,或者數字進行的操做,你均可以對function進行。你能夠將他們做爲參數傳遞給另外的函數(回調函數),將它們賦值給其餘變量。這就是爲何說在JavaScript裏面函數是一等的了。
咱們能夠將函數賦值給變量,例如:
const square = function (x) {
return x * x;
}
// 打印出25
square(5);
複製代碼
咱們還能夠將它繼續傳給其它的變量,例如:
const foo = square;
// 打印出36
foo(6);
複製代碼
咱們能夠將函數做爲參數傳遞給其餘函數。例如:
function formalGreeting() {
console.log("How are you?");
}
function casualGreeting() {
console.log("What's up?")
}
function greet(type, greetFormal, greetCasual) {
if (type === 'formal') {
greetFormal();
} else if (type === 'casual') {
greetCasual();
}
}
// 打印 What's up
greet('casual', formalGreeting, casualGreeting)
複製代碼
如今咱們知道什麼是一等函數了,讓咱們再深刻理解一下JavaScript裏面什麼是高階函數。
高階函數是那些操做其餘函數的函數。用最簡單的話來講,高階函數就是一個將函數做爲參數或者返回值的函數。
例如Array.prototype.map
, Array.prototype.filter
和Array.prototype.reduce
是JavaScript原生的高階函數。
讓咱們看一下一些使用原生高階函函數的例子,並將它們和不使用高階函數的狀況進行對比。
map()
函數會建立一個新的數組,數組裏面的元素是傳進來的函數(callback)調用原來數組相同位置的元素的返回值。
傳給map()
的回調函數callback接收三個參數:element
,index
和array
。
讓咱們來看一些例子:
假設咱們如今有一個整形數組,而後想要新建一個數組,新建的數組的元素是原來數組各個位置的數字的兩倍。讓咱們看一下如何用非高階函數的方法來解決問題:
const arr1 = [1, 2, 3];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * 2);
}
// 打印出 [2, 4, 6]
console.log(arr2)
複製代碼
map
const arr1 = [1, 2, 3];
const arr2 = arr1.map(function(item) {
return item * 2;
});
console.log(arr2);
複製代碼
咱們能夠用更簡潔的箭頭函數:
const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item * 2);
console.log(arr2);
複製代碼
假設咱們如今有一個存儲人們出生年份的數組,想要得到一我的們年齡的數組。
const birthYear = [1975, 1997, 2002, 1995, 1985];
const ages = [];
for(let i = 0; i < birthYear.length; i++) {
let age = 2018 - birthYear[i];
ages.push(age);
}
// 打印 [ 43, 21, 16, 23, 33 ]
console.log(ages);
複製代碼
map
const birthYear = [1975, 1997, 2002, 1995, 1985];
const ages = birthYear.map(year => 2018 - year);
// 打印 [ 43, 21, 16, 23, 33 ]
console.log(ages);
複製代碼
filter()
函數建立一個新的數組,數組裏面存儲原數組裏面能夠經過傳進來的callback測試的元素。傳給filter()
的回調函數接收三個參數:element
, index
和array
。
讓咱們看一下例子:
假設咱們如今有一個person對象數組,數組裏面的person有name和age這兩個屬性。咱們想要新建一個數組,這個數組只包含那些那些已經成年的人(年齡大於或者等於18歲)。
const persons = [
{ name: 'Peter', age: 16 },
{ name: 'Mark', age: 18 },
{ name: 'John', age: 27 },
{ name: 'Jane', age: 14 },
{ name: 'Tony', age: 24},
];
const fullAge = [];
for(let i = 0; i < persons.length; i++) {
if(persons[i].age >= 18) {
fullAge.push(persons[i]);
}
}
console.log(fullAge);
複製代碼
filter
const persons = [
{ name: 'Peter', age: 16 },
{ name: 'Mark', age: 18 },
{ name: 'John', age: 27 },
{ name: 'Jane', age: 14 },
{ name: 'Tony', age: 24},
];
const fullAge = persons.filter(person => person.age >= 18);
console.log(fullAge);
複製代碼
reduce
方法用被調用數組的元素依次做爲參數調用傳進來的callback而後產生一個返回值。reduce函數接收兩個參數:1) reducer函數(callback), 2) 一個可選的參數intialValue
做爲初始值。
reducer函數接收四個參數:accumulator
, currentValue
, currentIndex
和sourceArray
。
若是有初始值initialValue
,reducer第一次被調用的時候accumulator
等於initialValue
並且currentValue
等於數組的第一個元素。
若是沒有初始值initialValue
, reducer第一次被調用的時候accumulator
等於數組裏面的第一個元素,currentValue
等於數組的第二個元素。
假設咱們如今要對一個數組求和。
reduce
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
});
// 打印 25
console.log(sum);
複製代碼
reducer會用數組裏面的元素currentValue
依次執行,accumulator
保留着上次從reducer函數返回的結果。最後一次reducer調用的結果做爲reduce函數的返回值並被存儲在sum
變量裏面。
咱們也能夠爲這個方法提供一個初始值:
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 10);
// 打印出 35
console.log(sum);
複製代碼
const arr = [5, 7, 1, 8, 4];
let sum = 0;
for(let i = 0; i < arr.length; i++) {
sum = sum + arr[i];
}
// 打印出 25
console.log(sum);
複製代碼
從上面的例子你能夠看出,使用高階函數可使咱們的代碼更加乾淨,準確和簡潔。
到如今爲止咱們已經看了幾個JavaScript原生的高階函數。如今讓咱們來建立本身的高階函數。
讓咱們想象一下JavaScript沒有本身原生的map方法。咱們能夠本身用高階函數的方法來實現一個相似功能的函數。
假設咱們如今有一個字符串的數組,咱們想把這個數組轉換成一個整形的數組,這個數組裏面的元素是原來數組對應位置的字符串的長度。
const strArray = ['JavaScript', 'Python', 'PHP', 'Java', 'C'];
function mapForEach(arr, fn) {
const newArray = [];
for(let i = 0; i < arr.length; i++) {
newArray.push(
fn(arr[i])
);
}
return newArray;
}
const lenArray = mapForEach(strArray, function(item) {
return item.length;
});
// 打印出 [ 10, 6, 3, 4, 1 ]
console.log(lenArray);
複製代碼
上面的例子中,咱們建立了一個高階函數mapForEach
,這個函數接收一個數組和回調函數fn
做爲參數。這個高階函數循環遍歷數組裏面的每個元素,將各個元素做爲參數調用fn
,並將fn
的返回值用newArray.push
方法存儲在newArray中。
回調函數fn
接收原數組的元素做爲參數並返回該元素的長度,這個長度會被存儲在newArray
裏面。當for循環結束後,新的數組newArray
會被返回並賦值給lenArray
。
咱們學習了什麼是高階函數以及瞭解了一些原生的高階函數。咱們也學習瞭如何建立本身的高階函數。
簡而言之,高階函數就是一個接受其餘函數做爲輸入甚至能夠返回其它函數的函數。高階函數和普通函數實際上是同樣的,不過它有一個額外的能力就是接受函數做爲參數,或者返回值是函數。