本文深入解析JavaScript单线程模型下的阻塞问题,通过Ajax异步请求与setTimeout示例,阐明Event Loop(事件循环)的工作机制。详细讲解调用栈、回调队列与任务调度的关系,揭示异步代码的实际执行顺序,帮助开发者掌握异步编程核心原理。

JavaScript单线程与阻塞问题
假设浏览器正在执行一个复杂的图像转换算法,由于JavaScript是单线程运行的,此时浏览器进程会被阻塞。这导致界面无法渲染,其他代码也无法执行,应用界面将失去与用户的交互能力。
通常情况下,单个此类操作可能不会造成严重问题,但如果浏览器同时运行多个类似的计算密集型算法,这种阻塞现象就会变得非常严重。
具备一定经验的JavaScript开发人员通常都理解代码的异步执行机制,特别是在使用Ajax进行网络请求时。
// ajax(..) is some arbitrary Ajax function given by a library
var response = ajax('https://example.com/api');
console.log(response);
// `response` won't have the response
如果在代码中直接获取Ajax响应,,你将无法得到预期的结果。
正确的做法是像这样,通过回调函数(callback function)的方式来获取和处理结果。
ajax('https://example.com/api', function(response) {
console.log(response); // `response` is now available
});
另外需要提醒的是,在的代码示例中,设置`async: false`永远不是一个好的做法。
// This is assuming that you're using jQuery
jQuery.ajax({
url: 'https://api.example.com/endpoint',
success: function(response) {
// This is your callback.
},
async: false // And this is a terrible idea
});
通过上述示例,我们可以理解利用异步函数能够有效解决类似浏览器阻塞的问题。
当然,我们也可以通过`setTimeout(callback, milliseconds)`实现相同的逻辑。如果你已经理解了异步概念,那么下面这段代码的执行结果会是什么呢?
function first() {
console.log('first');
}
function second() {
console.log('second');
}
function third() {
console.log('third');
}
first();
setTimeout(second, 1000); // Invoke `second` after 1000ms
third();
Event Loop机制与异步原理
那么,异步处理机制背后的原理究竟是什么?这里就需要引入事件循环(Event Loop)的概念了。

Event Loop包含一个简单的任务(Job)机制,用于监视调用栈(Call Stack)和回调队列(Callback Queue)。如果调用栈为空,它将从队列中取出第一个事件,并将其推送到调用栈中,从而有效地执行该事件。
这种迭代过程在事件循环中被称为Tick(滴答)。每个事件本质上只是一个函数回调。
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
执行这段代码,中的调用栈清晰地演示了事件循环的处理流程。

请注意,`setTimeout(...)`并不会自动将你的回调函数放入事件循环队列中。
它只是设置了一个计时器。当计时器到期时,浏览器会将你的回调放入事件循环中,以便在未来的某个Tick执行。但是,队列中可能已经添加了其他事件——你的回调不会立即执行。
setTimeout与异步执行顺序
网络上有很多关于如何在JavaScript中开始使用异步代码的文章和教程,它们建议用户使用`setTimeout(callback, 0)`。
现在你已经知道了Event Loop是如何工作的,以及setTimeout的运行机制。
console.log('Hi');
setTimeout(function() {
console.log('callback');
}, 0);
console.log('Bye');
尽管等待时间设置为 0 ms,但浏览器控制台中的结果如下所示:
尽管等待时间被设置为0毫秒,但浏览器控制台中的输出结果如下所示:
Hi
Bye