【译】理解 Javascript 中的高阶函数
如果你正在学习JavaScript,一定会遇到高阶函数这个术语。 虽然听起来很复杂,但事实并非如此。 JavaScript 适用于函数式编程的原因是它适合使用高阶函数。 高阶函数在JavaScript中广泛使用。 如果你已经用 JavaScript 编程了一段时间,你可能已经使用过它们只是不知道。要完全理解这个概念,首先必须了解函数式编程是什么以及第一类函数的概念。
什么是函数式编程
在大多数简单的术语中,函数编程是一种编程形式,你可以将函数作为参数传递给其他函数,并将它们作为值返回。在函数式编程中,我们在函数中思考和编码。 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);
注意 - 虽然这在JavaScript中完全有效,但这被认为是一种有害的做法。 不应该向函数对象添加随机属性,如果必须,请使用对象。 在JavaScript中,可以使用其他类型(如对象,字符串或数字)执行所有操作,可以使用函数。 可以将它们作为参数传递给其他函数(回调),将它们分配给变量并传递它们等等。这就是JavaScript中的函数被称为First-Class函数的原因。
将函数分配给变量
在JavaScript中,我在JavaScript中们可以把函数赋值给变量。例如:
const square = function(x) {
return x * x;
}
// prints 25
square(5);
我们也可以传递它们。 例如:
const foo = square;
// prints 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是语言中内置的一些高阶函数。
应用中的高阶函数
让我们看一下内置高阶函数的一些例子,看看它与我们不使用高阶函数的解决方案比较。
Array.prototype.map
map()方法通过调用作为输入数组中每个元素的参数提供的回调函数来创建一个新数组。 map()方法将从回调函数中获取每个返回的值,并使用这些值创建一个新数组。 传递给map()方法的回调函数接受3个参数:element,index和array。 我们来看一些例子:
例1
假设我们有一个数组,我们想要创建一个新数组,其中包含第一个数组的每个值的两倍。让我们看看如何使用和不使用高阶函数来解决问题。
没有高阶函数
const arr1 = [1, 2, 3];
const arr2 = [];
for(let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i] * 2);
}
// prints [ 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#
假设我们有一个包含不同人的出生年份的数组,我们想要创建一个包含其年龄的数组。 例如:
没有高阶函数
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);
}
// prints [ 43, 21, 16, 23, 33 ]
console.log(ages);
具有高阶函数的 map
const birthYear = [1975, 1997, 2002, 1995, 1985];
const ages = birthYear.map(year => 2018 - year);
// prints [ 43, 21, 16, 23, 33 ]
console.log(ages);
Array.prototype.filter
filter()方法创建一个新数组,其中包含通过回调函数提供的测试的所有元素。 传递给filter()方法的回调函数接受3个参数:element,index和array。 我们来看一些例子:
例1#
假设我们有一个包含名称和年龄属性的对象的数组。 我们想要创建一个仅包含完整年龄(年龄大于或等于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方法对调用数组的每个成员执行回调函数,从而产生单个输出值。 reduce方法接受两个参数:1)reducer函数(回调),2)和一个可选的initialValue。 reducer函数(回调)接受四个参数:accumulator,currentValue,currentIndex,sourceArray。 如果提供了initialValue,则累加器将等于initialValue,currentValue将等于数组中的第一个元素。 如果没有提供initialValue,则累加器将等于数组中的第一个元素,currentValue将等于数组中的第二个元素。
例1#
假设我们必须总结一组数字:
高阶函数的 reduce
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
});
// prints 25
console.log(sum);
每次在数组中的每个值上调用reducer函数时,累加器都会保留reducer函数返回的先前操作的结果,并将currentValue设置为数组的当前值。 最后,结果存储在sum变量中。 我们还可以为此函数提供初始值:
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 10);
// prints 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];
}
// prints 25
console.log(sum);
您可以看到使用高阶函数使我们的代码更清晰,更简洁,更简洁。
创建我们自己的高阶函数
到目前为止,我们看到了语言中内置的各种高阶函数。 现在让我们创建自己的高阶函数。 我们假设JavaScript没有原生地图方法。 我们可以自己构建它,从而创建我们自己的高阶函数。 假设我们有一个字符串数组,我们希望将此数组转换为整数数组,其中每个元素表示原始数组中字符串的长度。
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;
});
// prints [ 10, 6, 3, 4, 1 ]
console.log(lenArray);
在上面的例子中,我们创建了一个高阶函数mapForEach,它接受一个数组和一个回调函数fn。此函数循环遍历提供的数组,并在每次迭代时调用newArray.push函数调用内的回调函数fn。 回调函数fn接收数组的当前元素并返回该元素的长度,该元素存储在newArray中。 for循环完成后,返回newArray并将其分配给lenArray。
结论
我们已经了解了高阶函数和一些内置的高阶函数。我们还学习了如何创建自己的高阶函数。 简而言之,高阶函数是一个函数,可以接收函数作为参数,甚至可以返回一个函数。高阶函数就像常规函数一样,具有接收和返回其他函数的附加能力,即参数和输出。

转载自:https://juejin.cn/post/6844903906250604552