为什么javascript回调作为参数传递?

问题描述 投票:1回答:5

我试图第一次理解回调。在我看到的所有示例中,回调总是作为参数传递。这是一个常见的例子:

let result = 0;

function add(num1, num2, callback) {
    setTimeout(() => {
        result = num1 + num2;
        callback();
    }, 2000);
}

function logResult() {
    console.log(result);
}

add(4, 5, logResult); // here's the callback passed as argument

使用以下代码可以获得相同的结果。并且它不需要将回调作为参数传递。

let result = 0;

function add(num1, num2) {
    setTimeout(() => {
        result = num1 + num2;
        logResult();
    }, 2000);
}

function logResult() {
    console.log(result);
}

add(4, 5);

它只是为了可读性和理解代码更好,它们作为参数传递?还是有什么我想念的?请有人赐教我吗?

javascript asynchronous callback
5个回答
6
投票

您的示例中根本不需要回调,您可以这样做:

function add(num1, num2) {
  setTimeout(() => { // thats a callback too, just saying ...
    const result = num1 + num2; // don't leak variables, declare them!
    console.log(result);
  }, 2000);
}

然而,编程就是创建可重用代码,然后可以将其组合成更复杂的程序。因此,您不希望将add的使用限制为记录结果,而是如果您接受回调,则可以使用它来实现各种任务:

 add(1, 2, (result) => {
   add(result, 5, (result2) => {
    alert(result2);
   });
});

3
投票

它不需要将回调作为参数传递。

是的,它确实…

setTimeout(() => {

一个用箭头函数定义的回调传递给setTimeout

(我知道你的意思是功能callback,但这仍然是一个回调并证明了我的观点)


由于setTimeout不是您定义的函数,因此在可以达到的范围内定义函数的唯一方法是使其成为全局函数。

然后,如果你想同时运行两个setTimeout实例,你可以将第一个回调分配给第一个全局然后第二个回调......好吧......你会被卡住。


3
投票

回调是一种解耦代码的机制。例如,假设add是API的一部分,使用第一个代码,我可以写:

add(4, 5, console.log);
add(4, 5, alert);
add(4, 5, writeOnTheFileSystemIfNodeJS);
add(4, 5, addToTheDOM);

// etc.

这对你的第二个代码是不可能的:它太耦合,所以我需要不同版本的add函数来完成以上四个方面的所有:addConsoleaddAlert等。不仅:使用回调你提供一个交易机制用你无法预料的逻辑。也许开发人员想要在canvas元素上添加结果,而你没有提供addCanvas的东西。但是通过回调,即使原则上没有为此目的设计也可以实现。

但是请注意,现在这种操作 - 发生一次 - 你可能会使用Promises,因为它们与await / async工作得很好,对于可能多次发生的事情,你可能会想要使用事件(例如addEventListener),或者流 - 在一个特写功能中,你会使用异步队列,这要归功于async iterators, and for await


1
投票

首先,明智的一句话:回调被认为是不好的做法。我们现在有更好的方法来处理这类事情。作为语言规范的一部分,我们有Promises。就外部图书馆而言,我们也有Observables。在它成为规范的一部分之前,使用回调构建了Promise,但为您提供了一种更易读的处理方式,特别是在回调链方面。

非常特别是因为通常在库代码中使用回调,而不是主代码,因此库开发人员正在添加一种方法,以便为其行为添加自定义功能。至于在您自己的代码中使用回调...根据应用程序的当前状态以及函数的调用者,您可能需要不同的回调。最重要的是,separation of concerns是您需要熟悉的重要概念。

例如,

function showModal(whichModal) {
  someLibrary.modal(whichModal).show();
  switch (whichModal) {
    case 'createUser':
      someUserLogic();
      break;
    case 'createProject':
      someProjectLogic();
      break;
  }
}

function createUser() {
  showModal('createUser');
}

function createProject() {
  showModal('createProject');
}

VS

function showModal(whichModal, postShowCallback) {
  someLibrary.modal(whichModal).show();
  postShowCallback();
}

function createUser() {
  showModal('createUser', someUserLogic);
}

function createProject() {
  showModal('createProject', someProjectLogic);
}

您可以看到第一个示例失控的速度有多快,第二个示例如何优雅地解决了这个问题


0
投票

将回调作为参数传递的一个原因是避免确定范围问题。您调用logResult时,可能无法定义示例中的add函数。此外,logResult可能会发生变异。考虑以下:

let result = 0;

function add(num1, num2) {
    setTimeout(() => {
        result = num1 + num2;
        logResult();
    }, 2000);
}

function logResult() {
    console.log(result);
}

add(4, 5);

function logResult() {
    console.log(2);
}

在上面的片段中,logResult函数是hoisted,原始版本被覆盖。请注意,尽管在第二次add(4, 5)声明之前调用了logResult,但仍然发生了这种情况。

通过接受回调参数可以避免此问题和其他范围问题。

function add(a, b, callback) {
    setTimeout(() => {
        callback(a + b);
    }, 2000);
}

add(4, 5, (result) => setTimeout(() => console.log(result), 2000));
© www.soinside.com 2019 - 2024. All rights reserved.