Fabrício 的回答恰到好处;但我想用技术性较低的东西来补充他的答案,它侧重于一个类比来帮助解释异步性的概念。
一个比喻...
昨天,我正在做的工作需要一位同事提供一些信息。我给他打了电话;谈话是这样进行的:
我:嗨鲍勃,我需要知道我们如何富“d的酒吧”上周天。吉姆想要一份关于它的报告,而你是唯一知道它细节的人。
鲍勃:当然可以,但我需要大约 30 分钟?
我:那是伟大的鲍勃。得到信息后给我回电!
说到这里,我挂了电话。因为我需要 Bob 的信息来完成我的报告,所以我离开了报告并去喝咖啡,然后我赶上了一些电子邮件。40 分钟后(Bob 很慢),Bob 回电并给了我我需要的信息。在这一点上,我继续我的报告工作,因为我拥有了我需要的所有信息。
想象一下,如果谈话变成这样;
我:嗨鲍勃,我需要知道我们如何富“d的酒吧”上周天。吉姆想要一份关于它的报告,而你是唯一知道它细节的人。
鲍勃:当然可以,但我需要大约 30 分钟?
我:那是伟大的鲍勃。我会等待。
我坐在那里等着。并等待。并等待。40 分钟。除了等待什么都不做。最后,鲍勃给了我信息,我们挂了电话,我完成了报告。但我失去了 40 分钟的生产力。
这是异步与同步行为
这正是我们问题中所有示例中发生的情况。加载图像、从磁盘加载文件以及通过 AJAX 请求页面都是缓慢的操作(在现代计算的上下文中)。
与等待这些慢速操作完成不同,JavaScript 允许您注册一个回调函数,该函数将在慢速操作完成时执行。然而,与此同时,JavaScript 将继续执行其他代码。JavaScript在等待缓慢操作完成的同时执行其他代码的事实使行为异步。如果 JavaScript 在执行任何其他代码之前等待操作完成,这将是同步行为。
var outerScopeVar;
var img = document.createElement('img');
// Here we register the callback function.
img.onload = function() {
// Code within this function will be executed once the image has loaded.
outerScopeVar = this.width;
};
// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);
在上面的代码中,我们要求 JavaScript 加载lolcat.png
,这是一个缓慢的操作。一旦这个缓慢的操作完成,回调函数将被执行,但与此同时,JavaScript 将继续处理下一行代码;即alert(outerScopeVar)
。
这就是我们看到警报显示的原因undefined
;因为alert()
是立即处理的,而不是在图像加载后处理。
为了修复我们的代码,我们所要做的就是将alert(outerScopeVar)
代码移动到回调函数中。因此,我们不再需要将outerScopeVar
变量声明为全局变量。
var img = document.createElement('img');
img.onload = function() {
var localScopeVar = this.width;
alert(localScopeVar);
};
img.src = 'lolcat.png';
你总是会看到回调被指定为一个函数,因为这是 JavaScript 中定义一些代码的唯一*方式,但直到以后才执行它。
因此,在我们所有的例子中,function() { /* Do something */ }
都是回调;要修复所有示例,我们要做的就是将需要操作响应的代码移到那里!
* 从技术上讲,您也可以使用eval()
,但为此目的eval()
是邪恶的
如何让来电者保持等待?
您目前可能有一些与此类似的代码;
function getWidthOfImage(src) {
var outerScopeVar;
var img = document.createElement('img');
img.onload = function() {
outerScopeVar = this.width;
};
img.src = src;
return outerScopeVar;
}
var width = getWidthOfImage('lolcat.png');
alert(width);
然而,我们现在知道这return outerScopeVar
会立即发生;在onload
回调函数更新变量之前。这导致getWidthOfImage()
返回undefined
,并且undefined
被警告。
为了解决这个问题,我们需要允许函数调用getWidthOfImage()
注册一个回调,然后将宽度的警报移动到该回调内;
function getWidthOfImage(src, cb) {
var img = document.createElement('img');
img.onload = function() {
cb(this.width);
};
img.src = src;
}
getWidthOfImage('lolcat.png', function (width) {
alert(width);
});
...和以前一样,请注意我们已经能够删除全局变量(在本例中为width
)。