【DailyENJS第二期】理解JavaScript中的高阶函数

DailyENJS 致力于翻译优秀的前端英文技术文章,为技术同学带来更好的技术视野。

image

如果你正在学习JavaScript,那么你肯定遇到过高阶函数这个术语。虽然听起来很复杂,但事实并非如此。

使JavaScript适合函数式编程的原因是它拥有高阶函数。

高阶函数在JavaScript中被广泛使用。如果你已经使用了 JavaScript 一段时间,你可能不知不觉已经使用了它们。要完全理解这个概念,首先必须了解函数式编程是什么以及 First-Class Functions 的概念。

函数式编程是什么

在大多数简单的术语中,函数式编程是一种编程形式,你可以将函数作为参数传递给其他函数,并且也可以将它们作为值返回。在函数式编程中,我们以函数思维进行思考和写代码。

JavaScript,Haskell,Clojure,Scala和Erlang都是实现函数式编程的语言。

First-Class Functions

如果你一直在学习JavaScript,你可能听说过JavaScript将函数视为一等公民。那是因为在JavaScript或任何其他函数式编程语言中,函数都是对象。

在JavaScript中,函数是一种特殊类型的对象。它们是Function对象。例如:

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

为了证明函数是JavaScript中的对象,我们可以这么做:

// We can add properties to functions like we do with objects
greeting.lang = 'English';
// Prints 'English'
console.log(greeting.lang)

注意: 虽然这在JavaScript中完全OK,但这被认为是一种有害的做法。你不应该向函数对象添加随机属性,如果必须添加属性,请使用对象。

在JavaScript中,你可以使用使用函数就像使用其他类型(如对象,字符串或数字)一样。你可以将它们作为参数传递给其他函数(回调),将它们分配给变量并传递它们等等。这就是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();
  }
}
// prints 'What's up?'
greet('casual', formalGreeting, casualGreeting);

现在我们知道了什么是 first-class functions ,让我们深入了解JavaScript中的高阶函数。

高阶函数

高阶函数是对其他函数进行操作的函数,可以将函数作为参数传递或者返回一个函数。简单来说,高阶函数是一个函数,它接收函数作为参数或将函数作为输出进行返回。

例如,Array.prototype.mapArray.prototype.filterArray.prototype.reduce 是语言中内置的一些高阶函数。

实践高阶函数

让我们看一下内置高阶函数的一些例子,看看它与我们不使用高阶函数的解决方案的比较。

Array.prototype.map

map() 方法通过调用一个回调函数来创建一个新数组。map() 方法将从回调函数中获取每个返回的值,并使用这些值创建一个新数组。

map()方法的回调函数接受3个参数:elementindexarray

来看一些例子:

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

使用高阶函数:

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() 的回调函数接受三个参数:element, indexarray

来看一些例子:

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

使用高阶函数:

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 函数(回调)接受四个参数:accumulatorcurrentValuecurrentIndexsourceArray

如果提供了 initialValue,则累加器将等于initialValue,currentValue将等于数组中的第一个元素。

如果没有提供initialValue,则累加器将等于数组中的第一个元素,currentValue将等于数组中的第二个元素。

例子1

假设我们必须累加一组数字得到和:

使用高阶函数:

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没有原生 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;
});
// prints [ 10, 6, 3, 4, 1 ]
console.log(lenArray);

在上面的例子中,我们创建了一个高阶函数 mapForEach,它接受一个数组和一个回调函数fn。

回调函数 fn 接收数组的当前元素并返回该元素的长度,该元素存储在newArray中。for 循环完成后,返回 newArray 并将其赋值给 lenArray

结论

我们已经了解了高阶函数和一些内置的高阶函数。我们还学习了如何创建自己的高阶函数。简而言之,高阶函数是一个函数,可以接收函数作为参数,甚至可以返回一个函数。高阶函数就像常规函数一样,具有接收和返回其他函数的额外能力,即参数和输出。

原文: https://blog.bitsrc.io/understanding-higher-order-functions-in-javascript-75461803bad

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,780评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,424评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,397评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,576评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,997评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,945评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,107评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,850评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,625评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,804评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,285评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,613评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,291评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,164评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,963评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,096评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,886评论 2 278

推荐阅读更多精彩内容