javascript中的高阶函数

javascript的高阶函数特性使得它能做函数化编程,高阶函数是指那些能把函数作为参数传入或者返回函数的函数。

一等函数

你一定对这句话有所耳闻——“javascript中的函数是一等公民”。这也就意味着在JavaScript中,函数被作为对象来看待。不知道你还记不记得,在之前的一篇《原始值vs.引用》的文章中,我们将数组、对象和函数统称为对象:JavaScript中原始值 vs. 引用值

函数可以像原始值那样被赋值来赋值去的,不过只是引用的拷贝,并且函数还可以作为函数的参数传入,或作为函数的返回值返回,就像原始值被当作函数参数传入和被返回一样,唯一不同的是传入和返回的函数实际上是指向那个函数的地址的引用。

这种JavaScript内置的原生本领赋予JS一种特殊的力量,尤其是在函数化编程中该特性(函数作为参数传入和返回)尤为重要。因为函数是对象,这门语言支持一种非常自然的手段来实现函数化编程。事实上,这实在是太理所应当和习以为常了,以至于你会疑惑我有必要对该特性如此大加赞赏吗?!

函数参数(函参)

如果你是一名使用JavaScript的前端开发工程师,你或许曾向函数中传递过回调函数。一旦函数中所有的操作执行完毕,回调函数会在函数执行结束后执行。通常,回调函数会作为某个函数的最后一个实参传入,而且常常这种回调函数是直接写在实参的位置上——称为匿名函数。

众所周知,JavaScript是单线程的,这意味着某一时刻只能执行一项操作,每一项操作按照在事件队列中的排列顺序在这条单线程上顺序触发。这种等待其他函数执行完毕后才能执行其他函数的语言特性是支持高阶函数的一大原因。JS允许异步行为,因此当一些耗时的操作没有返回结果前主线程可以继续执行其他操作。传入回调函数,当耗时的操作返回结果后执行回调函数。

在网络编程环境中这一点非常的重要,一个脚本可以向服务器发送Ajax请求,当接收到响应后处理响应结果。不需要知道Ajax请求到达服务器的时间或者服务器处理消息的时间。Node.js也经常使用到回调函数来高效的利用服务器资源。这种机制在当应用执行函数前等待用户输入的场景中也很有用。

举个例子,考虑下面一段简单的js代码,给鼠标添加一个事件监听器:

<button id="clicker">So Clickable</button>
document.getElementById("clicker").addEventListener("click", function() {
  alert("you triggered " + this.id);
});

这段脚本使用了一个匿名函数触发警告窗。当然,也可以将这个匿名函数的引用赋值给一个变量,这样读起来会容易一些。

var proveIt = function() {
  alert("you triggered " + this.id);
};
document.getElementById("clicker").addEventListener("click", proveIt);

注意,我们给 addEventListener 方法传入的是 proveIt 而不是 proveIt(),前者是函数的引用,后者是函数执行后的返回值。

我们的proveIt()函数在结构上依赖于其周围的代码环境,它总是返回id——无论是哪一个被触发的元素。这个函数能存在于任何上下文中——取决于你想要显示哪个元素的id,并且这个函数可以作为任何事件的处理函数。

除了使用匿名函数,这种独立存在的函数为我们开启了一扇通往无所不能的大门。就像我们尝试去写一个纯函数——不改变任何额外的数据,并每次为相同的输入返回相同的结果。我们现在有了一个至关重要的工具——帮助我们开发一个小小的库,这个库函数可以应用到任何应用中。

返回函数

除了能将函数作为参数传入另一个函数,JavaScript允许函数将另一个函数作为结果返回,这很好理解,因为函数可以被看作是对象,因此它可以像其他值那样被返回。

但是,将函数作为结果返回意味着什么?在另一个函数中定义一个将被返回的新函数——这就像是模版一样。这又开启了通往另一个世界的一扇大门——JavaScript的魔法函数。

举个板栗,想象你已经厌倦了在这篇文章中看到某人的名字,你希望把所有这个看腻的名字替换成你想要的名字。这时,你可以简单的写一个执行文本替换的函数——能替换任何你传入的文本。

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.

你看,代码其作用了,但它只适用于一种情况——从millenials替换为Snake People。你还想替换其他的名字。因此你需要改进这一函数来自定义要替换的文本,而不是:

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.

我们想要达到的目标是,不固定待替换和替换文本以及源文本三个参数,而是在使用过程中通过传递参数灵活变动。

收获

高阶函数是书写JavaScript代码时经常用到的语法特性,即使你不知道高阶函数这个名词,但你已经在工作中使用过他们了,每次你传递一个匿名函数或者一个回调函数,或者给某一函数传入值并将返回的新函数作为参数传给另一个函数。

这种从函数中返回另一个新函数的能力赋予JavaScript很便利的能力,允许你创建自定义名字的函数——执行特定的任务。这些代码片段的积累能让我们少走很多路,避免代码的重复,让我们的代码清晰和易读。

请使用Github账号登录留言