JavaScript中的高阶函数

如果您正在学习JavaScript,那么您一定已经遇到了“高阶函数”的概念。 这看起来很复杂,但实际上并非如此。

JavaScript适用于函数式编程,因为它支持高阶函数的概念。 这些功能在该语言中得到了广泛的使用,如果您使用JS编程,那么您可能甚至在不了解它们的情况下就已经使用了它们。



为了完全理解该概念,您首先需要了解函数式编程(Functional Programming)的概念以及什么函数是一流的(First-Class Functions)。

该材料(我们翻译的译本)供初学者使用,旨在解释高阶函数的概念,并演示如何在JavaScript中使用它们。

什么是函数式编程?


如果用简单的词来描述函数式编程的概念,那么事实证明这是一种编程方法,使用该方法,您可以将函数作为参数传递给其他函数,并将函数作为其他函数返回的值。 从事函数式编程,我们设计应用程序的体系结构,并使用函数编写代码。

支持功能编程的语言包括JavaScript,Haskell,Clojure,Scala和Erlang。

一流的功能


如果您正在学习JavaScript,您可能会听说语言将函数视为一等对象。 这是因为与其他支持函数式编程的语言一样,在JavaScript中函数是对象。

特别是在JS函数中,它们表示为特殊类型的对象-这些是Function类型的对象。 考虑一个例子:

 function greeting() { console.log('Hello World'); } //   greeting();  //  'Hello World' 

为了证明JavaScript中的函数是对象,我们可以继续前面的示例,进行以下操作:

 //     ,       greeting.lang = 'English'; //  'English' console.log(greeting.lang); 

请注意,虽然将自己的属性添加到JavaScript中的标准对象不会引起错误消息,但不建议这样做。 您不应将自己的属性添加到函数中。 如果您需要在对象中存储某些内容,则最好为此创建一个特殊的对象。

在具有函数的JavaScript中,您可以执行与其他类型的实体(例如ObjectStringNumber 。 可以将函数作为参数传递给其他函数。 此类函数已转移给其他人,通常充当回调函数(回调)。 可以将函数分配给变量,存储在数组中,依此类推。 这就是为什么JS中的函数是一流的对象。

将函数分配给变量和常量


可以将函数分配给变量和常量:

 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.mapArray.prototype.filterArray.prototype.reduce是高阶函数。

高阶函数的作用


考虑使用JS内置的高阶函数的示例,并将这种方法与不使用此类函数的类似操作进行比较。

▍方法Array.prototype.map


map()方法创建一个新的数组,调用作为参数传递给它的回调以处理输入数组的每个元素。 此方法获取回调返回的每个值,并将其放在输出数组中。

传递给map()的回调函数采用三个参数: element (元素), index (索引)和array (数组)。 让我们看一些例子。

示例1


假设我们有一个数字数组,我们想创建一个包含将这些数字乘以2的结果的新数组。考虑使用和不使用高阶函数来解决此问题的方法。

在不使用高阶函数的情况下解决问题


 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); 

使用高阶函数图解决问题


 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


假设我们有一个包含某些人出生年份的数组,我们需要创建一个数组来确定他们的年龄在2018年。 与以前一样,以两种方式考虑此问题的解决方案。

在不使用高阶函数的情况下解决问题


 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); 

使用高阶函数图解决问题


 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()方法基于该数组创建一个新数组,原始数组的元素落入该数组,该数组对应于传递给此方法的回调函数中指定的条件。 与map()方法一样,此函数采用3个参数: elementindexarray

考虑一个示例,该示例的构造方式与考虑map()方法时相同。

例子


假设我们有一个包含对象的数组,这些对象的属性存储有关特定人群代表的姓名和年龄的信息。 我们需要创建一个数组,其中将仅包含有关该组的成年人代表(年龄已满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); 

使用高阶函数过滤器解决问题


 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()方法使用回调处理数组的每个元素,并将结果放入单个输出值。 此方法有两个参数:回调和可选的初始值( initialValue )。

回调接受四个参数: accumulator (accumulator), currentValue (当前值), currentIndex (当前索引), sourceArray (源数组)。

如果为initialValue提供了initialValue参数,则在方法开始时, accumulator将等于该值,并且已处理数组的第一个元素将被写入currentValue

如果未向方法提供initialValue参数,则将数组的第一个元素写入accumulator ,将第二个元素写入currentValue

例子


假设我们有一个数字数组。 我们需要计算其元素的总和。

在不使用高阶函数的情况下解决问题


 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); 

使用高阶函数来解决问题


首先,考虑使用reduce()方法而不给它一个初始值。

 const arr = [5, 7, 1, 8, 4]; const sum = arr.reduce(function(accumulator, currentValue) { return accumulator + currentValue; }); //  25 console.log(sum); 

每次使用传递给currentValue的回调(即数组的下一个元素)调用回调时,其accumulator参数都会包含上一个操作的结果,即上一次迭代从函数返回的结果。 该方法完成后,最终结果将变为常数sum

现在,让我们看看如果将初始值传递给reduce()方法,问题的解决方案将是什么样子。

 const arr = [5, 7, 1, 8, 4]; const sum = arr.reduce(function(accumulator, currentValue) { return accumulator + currentValue; }, 10); //  35 console.log(sum); 

如您所见,使用高阶函数使我们的代码更简洁,更简洁,更易于阅读。

创建自定义的高阶函数


到目前为止,我们一直在使用JS内置的高阶函数。 现在,让我们创建可以与其他函数一起使用的函数。

想象一下,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回调函数。 mapForEach函数在循环中mapForEach数组,并在该循环的每次迭代中调用fn回调。

fn回调接受数组的当前字符串元素,并返回此元素的长度。 fn函数返回的内容在newArray.push()命令中使用,并进入mapForEach()将返回的数组。 该数组最终将被写入lenArray常量。

总结


在本文中,我们讨论了高阶函数,并探讨了一些内置的JavaScript函数。 另外,我们想出了如何创建自己的高阶函数。

简而言之,可以说高阶函数的本质是可以将其他函数作为参数并返回其他函数作为其工作结果的函数。 使用高阶函数中的其他函数看起来与使用任何其他对象相同。

亲爱的读者们! 您必须编写自己的高阶函数吗?

Source: https://habr.com/ru/post/zh-CN428570/


All Articles