回调函数的有用性

Usefulness of callback functions

本文关键字:有用性 函数 回调      更新时间:2023-09-26

在Javascript中有可能定义一个函数X并将其作为参数传递给另一个函数y

这样的函数X称为callback function

你能解释一下为什么在一些清晰的例子中使用回调函数是有用的吗(例如,发送一些带有演示的小提琴链接)?我可以看到一个有用的地方,那就是代码的可读性,但我不确定,因为带有回调的代码看起来更复杂。

唯一有用的是它的使用在浏览器环境和异步执行在AJAX?那么其他的Javascript实现(比如Rhino)呢?回调在那里也有用吗?还是它们的有用性只取决于Javascript执行的环境?

谢谢

作为参数的回调

回调很有用,因为它们允许你用代码"配置"一个函数。

这方面的原型示例是一个排序函数,它允许您指定自己的排序标准。我将使用一个函数,根据某些条件返回数组的最小值,因为它比排序简单,但它仍然可以很好地用作示例:
function min(collection, property) {
    if (collection.length == 0) {
        return null;
    }
    var minIndex = 0;
    var minValue = collection[0][property];
    for (var i = 1; i < collection.length; ++i) {
        if (minValue > collection[i][property]) {
            minValue = collection[i][property];
            minIndex = i;
        }
    }
    return collection[minIndex];
}
var items = [
    { name: "John", age: 20 }, { name: "Mary", age: 18 }
    ];
alert(min(items, 'age').name); // prints "Mary"

查看运行的代码。有什么问题吗?

问题是,虽然我们为了创建一个可配置的min函数做了一些努力(它根据我们指定的任何属性找到最小值),但它在一般意义上仍然非常有限,因为它只能根据一个属性找到最小值。

如果我们的数据是这样呢?

var items = [ "John/20", "Mary/18" ];

这不再是一个对象数组,而是一个格式化字符串数组。它具有与原始集合相同的数据,但结构不同。因此,无法使用原min函数对其进行处理。

现在我们可以编写min的另一个版本,它使用字符串而不是对象,但这将是毫无意义的(有无限多的方法来表示相同的数据)。对于一个特定的情况,这可能是一个快速而肮脏的解决方案,但是考虑一个库编写者的问题:他们如何编写一个对每个人都有用的函数,而不知道每个情况下的数据将如何结构化?

营救回调

Simple:允许用户指定如何从数据中提取标准,从而使min函数更强大。这听起来很像"给函数代码从数据中提取标准",这就是我们要做的。min将接受回调:

function min(collection, callback) {
    if (collection.length == 0) {
        return null;
    }
    var minIndex = 0;
    var minValue = callback(collection[0]);
    for (var i = 1; i < collection.length; ++i) {
        if (minValue > callback(collection[i])) {
            minValue = callback(collection[i]);
            minIndex = i;
        }
    }
    return collection[minIndex];
}
var items = [ "John/20", "Mary/18" ];
var minItem = min(items, function(item) {
    return item.split('/')[1]; // get the "age" part
});
alert(minItem);

查看代码运行

现在我们的代码终于可以重用了。我们可以很容易地配置它,使其同时使用第一个第二种类型的数据,而不需要花费多少精力。

参见支持回调的函数处理两种类型的数据。这是我们获得极大灵活性的结果,因为我们允许用户以回调的形式提供作为函数一部分运行的代码。

异步编程中的回调

回调的另一个主要用途是启用异步编程模型。这在所有情况下都是有用的,当你计划一个操作完成,但不希望你的程序在操作完成之前停止运行。

为了使其工作,您为操作提供了一个回调函数,并有效地告诉它:去为我做这项工作,当你完成后,回叫我。然后,您可以自由地继续做任何您喜欢的事情,知道当结果可用时,您指定的函数将运行。

这在使用AJAX的web中是最明显的:您向web服务器发出请求,当收到响应时您以某种方式对其进行操作。您不希望在现场等待响应,因为它可能会花费很多时间(例如,如果有连接问题),并且您不希望您的程序在所有时间内都没有响应。

关于AJAX回调的示例,请参阅jQuery load函数的文档

在JavaScript中,大量的函数调用是异步的(例如使用XMLHTTPRequest执行AJAX请求)。因此,您不知道返回值何时到达,因为您的浏览器正在后台执行其他操作。

在不导致脚本其余部分停止运行的情况下,回调函数的概念允许您在异步调用完成其需要执行的操作后立即运行过程。

例如,假设我从某处请求foo.json。我会这样做(虚构代码):

ajaxGet("foo.json", function(data)
{
    doSomethingWith(data);
});
doSomethingElse();

回调函数将(function(data) { ... })将执行自己的协议时,ajaxGet已经检索到的数据,现在准备好了。在此之前,doSomethingElse()将执行并且不会挂起浏览器。


要回答你关于其他JavaScript环境的问题,让我们来看看node.js,可以说是最流行的。

现在,node.js是完全异步的,这意味着函数调用永远不会阻塞(理论上),因此你将不得不有回调来调用执行任何类型的异步I/O的回调。事实上,我想说node.js几乎是真正的延续传递风格,其中函数不返回并依赖回调来传递它们的值。

自AJAX以来,回调在JavaScript中非常流行。AJAX是对另一个Web资源的异步调用,因为您不知道它什么时候会结束,所以应用程序不需要等待响应。相反,它继续执行,并提供一个回调,以便在请求完成后立即调用。

这个简单的代码提供回调,显示从服务器检索到的产品:

$.get('/service/products', function(p) {
   alert(p);
});

此外,由于JavaScript本质上是函数式语言,函数在这里起着主要作用。您正在将函数传递给函数,或者从函数返回函数。它对于不同的模式实现非常有用,比如

function getPersonFactory(name) {
   var create = function () {
      return new Person(name);
   }
   return create;
}
var factory = getPersonFactory('Joe');
var person = factory();

由于JavaScript的函数性质和美观易用的回调编程,它发现了它对Node.js等框架的影响,Node.js是在IO操作上高度优化的JavaScript框架。

node . js HelloWorld:

var http = require("http");
http.createServer(function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}).listen(8888);

看,它创建HTTP服务器并为每个到达该服务器的请求提供回调。您将输出响应并结束它。非常紧凑和高效的代码。

对于核心函数式编程,您可以看看这篇很棒的文章:

http://matt.might.net/articles/implementation-of-recursive-fixed-point-y-combinator-in-javascript-for-memoization/

如果进行任何类型的事件侦听,回调都是不可避免的。JavaScript和事件驱动范式是兄弟。浏览器中呈现的用户交互是原因。

无论你在Node.js中监听DOM事件、Ajax完成、文件系统访问还是HTTP请求,你都必须定义一个在事件发生时调用的过程。

例如,更高级的回调形式是承诺。


同样,回调作为迭代处理器也很方便:

[ 1, 2, 3 ].forEach(function callback(val) { console.log(val); });

下面可以用它们的代码点替换所有的字母:

'hey hop hello'.replace(/./, function callback(s) { return s.charCodeAt(0); });

帮助异步函数调用。不必等待某个函数完成,您可以调用回调并将其传递给异步函数,然后继续当前的执行。一旦函数完成,就执行回调。Ajax使用回调。这清楚地解释了Ajax是如何使用回调的。

这里有一些类似的线程。

为什么在JavaScript中使用回调,它的优点是什么?

我如何利用异步XMLHttpRequest的回调函数?