以另一个函数作为参数,或者定义一个函数作为返回值的函数,被称为高阶函数。
JavaScript可以接受高阶函数。处理高阶函数的能力,以及其他特性,使得JavaScript成为适合函数式编程的编程语言之一。
JavaScript把函数视为一等公民
你可能听说过JavaScript函数是一等公民。这意味着JavaScript中的函数是对象。
它们的类型是Object,它们可以被赋值为变量的值,可以像任何其他引用变量一样被传递和返回。
一等函数赋予JavaScript特殊的能力,使我们能够受益于高阶函数。
由于函数是对象,JavaScript是支持函数式编程自然方式的流行编程语言之一。
事实上,一等函数对JavaScript的方法是如此本质,以至于我敢打赌,你甚至没有想到自己在使用它们。
高阶函数可以接受函数作为参数
如果你经常从事JavaScript Web开发,你可能遇到过使用回调函数的函数。
回调函数是在操作结束时执行的函数,一旦所有其他操作完成,它就会执行。
通常,我们在其他参数之后将此函数作为参数传递。它通常被定义为匿名函数。回调函数依赖于JavaScript处理高阶函数的能力。
JavaScript是一种单线程语言。这意味着一次只能执行一个操作。
为了避免操作相互阻塞或系统的主线程(这会导致死锁),引擎确保所有操作按顺序执行。它们沿着这条单线程排队,直到另一个代码事务的执行变得安全为止。
传递一个函数作为参数,并在父函数的其他操作完成后运行它的能力,对于一种语言来支持高阶函数至关重要。
JavaScript中的回调函数允许异步行为,因此脚本可以在等待结果的同时继续执行其他函数或操作。
在处理可能在未确定的时间后返回结果的资源时,传递回调函数的能力至关重要。
这种高阶函数模式在Web开发中非常有用。脚本可能会向服务器发送一个请求,然后需要在响应到达时处理响应,而不需要任何关于服务器网络延迟或处理时间的知识。
Node.js经常使用回调函数来高效利用服务器资源。在应用程序在执行函数之前等待用户输入时,这种异步方法也非常有用。
示例:将警告函数传递给元素事件监听器 考虑这段简单的JavaScript代码片段,它为一个按钮添加了一个事件监听器。
So Clickable
document.getElementById("clicker").addEventListener("click", function() {
alert("you triggered " + this.id);
});
这个脚本使用一个匿名内联函数来显示一个警告。
但是它同样可以使用一个单独定义的函数,并将该命名函数传递给addEventListener方法:
var proveIt = function() {
alert("you triggered " + this.id);
};
document.getElementById("clicker").addEventListener("click", proveIt);
我们不仅通过这样做演示了高阶函数。我们使我们的代码更易读、更具弹性,并将不同任务的功能分离开来(监听点击与向用户发出警报)。
高阶函数如何支持代码可重用性 我们的proveIt()函数在结构上与其周围的代码是独立的,总是返回触发元素的ID。这种函数设计方法是函数式编程的核心。
这段代码可以存在于任何显示具有元素ID的警报的上下文中,并可以使用任何事件侦听器调用。
用一个单独定义和命名的函数替换内联函数的能力开辟了一个全新的世界。
在函数式编程中,我们尝试开发不改变外部数据并且每次以相同输入返回相同结果的纯函数。
现在,我们拥有了一个重要的工具,可以帮助我们开发一组小型、有针对性的高阶函数库,在任何应用程序中通用使用。
注意:传递函数与传递函数对象 请注意,我们将proveIt传递给addEventListener函数,而不是proveIt()。
当您传递函数名称而没有括号时,您传递的是函数对象本身。
当您用括号传递它时,您传递的是执行该函数的结果。
使用高阶函数返回函数作为结果
除了接受函数作为参数,JavaScript还允许函数将其他函数作为结果返回。
这是有道理的,因为函数只是对象。对象(包括函数)可以被定义为函数返回的值,就像字符串、数组或其他值一样。
但是将函数作为结果返回意味着什么?
函数是分解问题和创建可重用代码片段的强大方式。当我们将一个函数定义为高阶函数的返回值时,它可以作为新函数的模板!
这打开了另一个函数式JavaScript魔法的世界。
假设你已经读了太多有关千禧一代的文章并感到无聊。你决定每次出现该词时将Millennials替换为短语Snake People。
你的冲动可能仅仅是编写一个函数,该函数对传递给它的任何文本执行该文本替换:
var snakify = function(text) {
return text.replace(/millenials/ig, "Snake People");
};
console.log(snakify("The Millenials are always up to something."));
// The Snake People are always up to something.
这样做可以解决问题,但是它非常特定于这种情况。也许你已经对有关婴儿潮一代的文章失去了耐心,你也想为他们创建一个自定义函数。
但即使是这样一个简单的函数,你也不想重复你已经写过的代码,而是希望从一个高阶函数开始。
var hippify = function(text) {
return text.replace(/baby boomers/ig, "Aging Hippies");
};
console.log(hippify("The Baby Boomers just look the other way."));
// The Aging Hippies just look the other way.
但是,如果你决定想要做一些更高级的操作,例如保留原始字符串中的大小写,那么你需要修改这两个新函数。
这很麻烦,而且会使你的代码更加脆弱和难以阅读。在这种情况下,我们可以使用高阶函数来解决问题。
构建一个高阶函数模板
你真正想要的是灵活性,能够在模板函数中将任何一个术语替换为另一个术语,并将该行为定义为一个基础函数,从而可以构建新的自定义函数。
使用分配函数作为返回值的能力,JavaScript提供了更方便的方法来实现这种情况:
var attitude = function(original, replacement, source) {
return function(source) {
return source.replace(original, replacement);
};
};
var snakify = attitude(/millenials/ig, "Snake People");
var hippify = attitude(/baby boomers/ig, "Aging Hippies");
console.log(snakify("The Millenials are always up to something."));
// The Snake People are always up to something.
console.log(hippify("The Baby Boomers just look the other way."));
// The Aging Hippies just look the other way.
我们所做的是将实际工作的代码封装成一个通用且可扩展的 attitude 函数。它封装了所有需要修改任何输入字符串并使用原始短语作为初始值,输出一些态度的替换短语所需的工作。
当我们将这个新函数定义为 attitude 高阶函数的引用时,预先填充它所需的前两个参数,我们得到了什么好处呢?它允许新函数接受任何传递给它的文本,并将该参数用作我们在 attitude 函数的输出中定义的返回函数的参数。
JavaScript 函数不关心你传递的参数数量。如果第二个参数缺失,它将把它视为 undefined。当我们选择不提供第三个参数或任何数量的额外参数时,它也会同样处理。
此外,您可以稍后传递该附加参数。当您定义要调用的高阶函数时,可以像刚才演示的那样将其定义为引用高阶函数返回的带有一个或多个未定义参数的函数。
如果需要,请多看几遍,以便您完全理解发生了什么。
我们正在创建一个模板高阶函数,它返回另一个函数。然后,我们将新返回的函数减去一个属性,定义为模板函数的自定义实现。
使用这种方式创建的所有函数都将继承来自高阶函数的工作代码。但是,您可以使用不同的默认参数预定义它们。
高阶函数是 JavaScript 工作的基础,你已经在使用它们了。
每次传递匿名或回调函数时,实际上是获取传递的函数返回的值,并将其用作另一个函数的参数(例如使用箭头函数)。
开发人员在学习 JavaScript 的过程中很早就熟悉了高阶函数。它是 JavaScript 设计的内在特性,可能直到后来才需要学习驱动箭头函数或回调的概念。
赋值返回其他函数的函数的能力扩展了 JavaScript 的方便性。高阶函数允许我们创建自定义命名函数,使用第一阶函数的共享模板代码执行专门的任务。
每个函数都可以继承在未来高阶函数中进行的任何改进。这有助于避免代码重复,并使我们的源代码干净而易读。
如果确保函数是纯函数(不改变外部值,并且对于任何给定的输入始终返回相同的值),则可以创建测试来验证在更新第一阶函数时代码更改不会破坏任何东西。 现在你已经知道了高阶函数是如何工作的,你可以开始考虑在自己的项目中如何利用这个概念。
JavaScript 的一个很棒的特性就是你可以将函数式编程技术与你已经熟悉的代码混合使用。
试着进行一些实验。即使你只是为了使用高阶函数而使用它们,你很快就会熟悉它们所提供的额外灵活性。
一点点高阶函数的工作可以让你的代码在未来数年中变得更好。