javascript事件循环解析

javascript 事件循环解析

用几张图来示意了解一下 事件循环和异步调用

先来个简单的 ajax 异步

让我们来看看下面的例子:

// ajax(..) is some arbitrary Ajax function given by a library
var response = ajax('https://example.com/api');

console.log(response);  // => undefined
// `response` won't have the response

以上代码中 ajax 是异步调用的,所以你会看见 代码执行的时候Ajax(..)函数没法给response变量赋予真正的返回值。

如果你想让它正常运行如下:

ajax('https://example.com/api', function(response) {
    console.log(response); // `response` is now available
});

开始我们的事件循环解析吧:

setTimeout(callback, milliseconds)
方法也可以实现异步。setTimeout函数所做的是设置一个事件(超时),以便稍后发生。让我们来看看:

function first() {
    console.log('first');
}
function second() {
    console.log('second');
}
function third() {
    console.log('third');
}
first();
setTimeout(second, 1000); // Invoke `second` after 1000ms
third();

输出如下:

first
third
second

解析事件循环

先看一张图吧: image 这些Web API是什么? 本质上,它们是您无法访问的线程,你可以对它们进行调用。它们是并行启动的浏览器的一部分。如果你是一个 Node.jS开发者,这些是C++API。

那什么又是事件循环呢?

image

事件循环有一个简单的工作——监视调用堆栈和回调队列。如果调用堆栈是空的,它将从队列中取出第一个事件,并将其推到调用堆栈,该堆栈有效地运行它。 这样的迭代被称为事件循环中的 一个标记 。每个事件都只是一个函数回调。

上面一堆废话后!让我们来具体看看到底里面 发生了什么鬼吧!

1. 状态是很清晰的. 浏览器控制台显示,调用堆栈是空的.

image

2. console.log(‘Hi’) 被添加到调用堆栈中.

image

3. console.log(‘Hi’) 执行.

image

4. console.log(‘Hi’) 从调用堆栈中删除.

image

#### 5. setTimeout(function cb1() { … }) 被添加到调用堆栈中. image

#### 6. setTimeout(function cb1() { … }) 执行。浏览器将作为Web api的一部分创建一个计时器。它将为你处理倒计时。 image

#### 7. The setTimeout(function cb1() { … }) 它本身是完整的,并且从调用堆栈中删除。

image

#### 8. console.log(‘Bye’) 被添加到调用堆栈中.

image

#### 9. console.log(‘Bye’) 执行.

image

#### 10. console.log(‘Bye’) 从调用堆栈中删除.

image

11. 在至少 5000 ms 后, 计时器完成并将cb1回调推到回调队列。

image

12. 事件循环从回调队列中获取cb1,并将其推到调用堆栈。

image

13. cb1 执行 和添加 console.log(‘cb1’) 到调用堆栈.

image

14. console.log(‘cb1’) 执行.

image

15. console.log(‘cb1’) 从调用堆栈中删除.

image

16. cb1 从调用堆栈中删除.

image

最后来个快速回顾吧:

image

值得注意的是,ES6指定了事件循环应该如何工作,这意味着从技术上讲,它被加入到JS引擎的职责范围内,不再只是托管环境中的一部分。造成这种变化的一个主要原因是在ES6中引入了Promise,因为后者需要对事件循环队列的调度操作进行直接的、细粒度的控制。


setTimeout(…) 是怎么样工作的

需要注意的是setTimeout(…)不会自动将您的回调放到事件循环队列中。它设置了一个计时器。

托管环境将您的回调放入事件循环中,以便将来的某个时间点将会接收并执行它。看看这段代码:

setTimeout(myCallback, 1000);

这并不意味着myCallback 将在1000毫秒时执行,而是在1000毫秒时,myCallback将被添加到队列中。不过,队列可能会有其他之前添加的事件——你的回调将不得不等待。

那么 setTimeout(callback, 0)的工作是什么呢?

现在你知道事件循环做了什么事情,以及setTimeout是如何工作的:调用setTimeout,并将0作为第二个参数只是推迟回调,直到调用堆栈为空。

console.log('Hi');
setTimeout(function() {
    console.log('callback');
}, 0);
console.log('Bye');

尽管等待时间设置为0毫秒,但浏览器控制台的结果如下:

Hi
Bye
callback