我试图第一次理解回调。在我看到的所有示例中,回调总是作为参数传递。这是一个常见的例子:
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);
它只是为了可读性和理解代码更好,它们作为参数传递?还是有什么我想念的?请有人赐教我吗?
您的示例中根本不需要回调,您可以这样做:
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);
});
});
它不需要将回调作为参数传递。
是的,它确实…
setTimeout(() => {
一个用箭头函数定义的回调传递给setTimeout
。
(我知道你的意思是功能callback
,但这仍然是一个回调并证明了我的观点)
由于setTimeout
不是您定义的函数,因此在可以达到的范围内定义函数的唯一方法是使其成为全局函数。
然后,如果你想同时运行两个setTimeout实例,你可以将第一个回调分配给第一个全局然后第二个回调......好吧......你会被卡住。
回调是一种解耦代码的机制。例如,假设add
是API的一部分,使用第一个代码,我可以写:
add(4, 5, console.log);
add(4, 5, alert);
add(4, 5, writeOnTheFileSystemIfNodeJS);
add(4, 5, addToTheDOM);
// etc.
这对你的第二个代码是不可能的:它太耦合,所以我需要不同版本的add
函数来完成以上四个方面的所有:addConsole
,addAlert
等。不仅:使用回调你提供一个交易机制用你无法预料的逻辑。也许开发人员想要在canvas元素上添加结果,而你没有提供addCanvas
的东西。但是通过回调,即使原则上没有为此目的设计也可以实现。
但是请注意,现在这种操作 - 发生一次 - 你可能会使用Promises,因为它们与await / async工作得很好,对于可能多次发生的事情,你可能会想要使用事件(例如addEventListener
),或者流 - 在一个特写功能中,你会使用异步队列,这要归功于async iterators, and for await
。
首先,明智的一句话:回调被认为是不好的做法。我们现在有更好的方法来处理这类事情。作为语言规范的一部分,我们有Promise
s。就外部图书馆而言,我们也有Observable
s。在它成为规范的一部分之前,使用回调构建了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);
}
您可以看到第一个示例失控的速度有多快,第二个示例如何优雅地解决了这个问题
将回调作为参数传递的一个原因是避免确定范围问题。您调用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));