【译】理解JavaScript的高阶函数

原文连接: Understanding Higher-Order Functions in JavaScript
原文做者: Sukhjinder Arora
译者: 进击的大葱
推荐理由: 本文详细介绍了什么是函数式编程以及如何写本身的高阶函数High-order functions。javascript

若是你有学习过JavaScript, 你必定有听太高阶函数这个词。高阶函数虽然听起来比较复杂,但是实际上并不难。java

JavaScript之因此很适合拿来进行函数式编程是由于它容许高阶函数的存在。编程

为了彻底理解高阶函数的概念,你必定要先理解什么是函数式编程(Functional Programming)以及一等类函数(First-class Functions)的概念。数组

什么是函数式编程(Functional Programming)

用最简单的话来讲,函数式编程就是将函数做为另一个函数的参数或者返回值。在函数式编程的世界里面,咱们用函数的方式进行思考和编码。bash

JavaScript,HasKell,Clojure,Scala和Erlang是一些支持函数式编程的语言。函数式编程

一等类函数(First-class Functions)

若是你使用过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.filterArray.prototype.reduce是JavaScript原生的高阶函数。

高阶函数实践

让咱们看一下一些使用原生高阶函函数的例子,并将它们和不使用高阶函数的状况进行对比。

Array.prototype.map

map()函数会建立一个新的数组,数组里面的元素是传进来的函数(callback)调用原来数组相同位置的元素的返回值。

传给map()的回调函数callback接收三个参数:elementindexarray

让咱们来看一些例子:

例子1

假设咱们如今有一个整形数组,而后想要新建一个数组,新建的数组的元素是原来数组各个位置的数字的两倍。让咱们看一下如何用非高阶函数的方法来解决问题:

不利用高阶函数
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);
复制代码
例子2

假设咱们如今有一个存储人们出生年份的数组,想要得到一我的们年龄的数组。

不使用高阶函数
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);
复制代码

Array.prototype.filter

filter()函数建立一个新的数组,数组里面存储原数组里面能够经过传进来的callback测试的元素。传给filter()的回调函数接收三个参数:element, indexarray

让咱们看一下例子:

例子1

假设咱们如今有一个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);
复制代码

Array.prototype.reduce

reduce方法用被调用数组的元素依次做为参数调用传进来的callback而后产生一个返回值。reduce函数接收两个参数:1) reducer函数(callback), 2) 一个可选的参数intialValue做为初始值。

reducer函数接收四个参数:accumulator, currentValue, currentIndexsourceArray

若是有初始值initialValue,reducer第一次被调用的时候accumulator等于initialValue并且currentValue等于数组的第一个元素。

若是没有初始值initialValue, reducer第一次被调用的时候accumulator等于数组里面的第一个元素,currentValue等于数组的第二个元素。

例子1

假设咱们如今要对一个数组求和。

使用高阶函数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

结论

咱们学习了什么是高阶函数以及了解了一些原生的高阶函数。咱们也学习了如何建立本身的高阶函数。

简而言之,高阶函数就是一个接受其余函数做为输入甚至能够返回其它函数的函数。高阶函数和普通函数实际上是同样的,不过它有一个额外的能力就是接受函数做为参数,或者返回值是函数

相关文章
相关标签/搜索