Event Loop 就是我们所熟知的事件轮询。

    前面一篇文章中,简单的介绍了一下异步,但是并没有说异步是怎么实现的。因此这篇文章主要讲一下 JS 实现异步的具体解决方案 —— Event Loop。

    先简单解释一下 Event Loop 实现异步的过程:

    1. 同步的代码直接执行
    2. 异步函数先放在异步队列中
    3. 待同步函数执行完毕,轮询执行异步队列的函数。
    1. setTimeout(function() {
    2. console.log(100)
    3. })
    4. console.log(200)
    5. // 执行结果
    6. // 200
    7. // 100

    上面的代码中在执行的时候,当遇到 setTimeout 之后,它会先将 setTimeout 的函数先放进去异步队列中先不执行,然后继续执行后面的同步函数,因此将上面的代码分为主进程跟异步队列两部分的话,会是下面的情况:

    1. // 主进程
    2. console.log(200)
    1. // 异步队列
    2. function() {
    3. console.log(100)
    4. }

    当主进程中的同步代码都执行完毕之后,JS 引擎会看看异步队列中是否存在需要执行的函数,如果存在,则拿到到主进程中执行。所以这就是执行结果先输出 200,再输出 100 的原因。


    再看看另外一个例子:

    1. setTimeout(function() {
    2. console.log(1)
    3. }, 100)
    4. setTimeout(function() {
    5. console.log(2)
    6. })
    7. console.log(3)
    8. // 执行结果
    9. // 3
    10. // 2
    11. // 1

    上面这个例子,有两个 setTimeout ,第一个有 100 毫秒延迟,第二个没有延迟。当执行代码的时候,碰到第一个 setTimeout 并不会立即放到异步队列中,而是会等到 100 毫秒之后,才会放到异步队列中。由于第二个 setTimeout 没有设置延迟,因此它会立即放到异步队列中。

    因此拆分成主进程和异步队列的话,会是下面的样子:

    1. // 主进程
    2. console.log(3)
    1. // 异步队列
    2. // 立即被放入
    3. function() {
    4. console.log(2)
    5. }
    6. // 100ms 之后被放入
    7. function() {
    8. console.log(1)
    9. }

    当主进程中的 console.log(3) 被执行完毕之后,js 引擎会看一下异步队列中是否存在函数需要执行。

    由于第二个 setTimeout 没有设置延迟,所以之前碰到这个 setTimeout 的时候,就已经将它的函数放到异步队列中了。因此在执行完主进程中的同步函数之后,JS 引擎会在异步队列中找到 function(){ console.log(2)} 这个方法,并将它放到主进程中去执行。

    function(){ console.log(1)} 执行完毕之后,JS 引擎又去看异步队列中是否有函数需要执行。需要注意哦,第一个 setTimeout 是设置了 100 毫秒的延迟的,因此 JS 引擎是没那么快在异步队列中找到第一个 setTimeout 的函数的。

    第一个 setTimeout 100 毫秒之后才将函数放到异步队列中,那此时 JS 引擎就会监视异步队列中是否有需要执行的代码。直到 100 毫秒之后,setTimeout 将函数放入到异步队列中,这时候 JS 引擎看到异步队列中有需要执行的函数,便将函数放到主进程中执行。

    所以轮询轮询,为什么叫轮询呀,就是因为 JS 引擎会先查看异步队列中是否有需要执行的代码,有需要执行的就拿到主进程中去执行。执行完毕之后主线程没东西执行了,JS 引擎有去看看异步队列有没有东西要执行。如果发现有东西需要执行,JS 引擎就将它放到主进程中执行。这个过程是一直循环重复的,因此它才叫轮询嘛。


    在前一个例子的基础上,再加上一个 Ajax 请求来分析一下:

    1. $.ajax({
    2. url: 'xxxxx',
    3. success: function (result) {
    4. console.log('a')
    5. }
    6. })
    7. setTimeout(function() {
    8. console.log('b')
    9. }, 100)
    10. setTimeout(function() {
    11. console.log('c')
    12. })
    13. console.log('d')

    由于这个例子只是比前面的例子多了一个 ajax 请求函数,因此重点分析一下 ajax 请求这部分。ajax 中的 success 是一个异步函数,当 ajax 请求成功之后,success 这个异步函数才会存放到异步队列中的。由于 ajax 到底什么时候请求成功这个是不知道的,因此到底是在 100 毫秒之前放到异步队列中还是在 100 毫秒之后放到异步队列中是不确定的,因此是先执行 success 异步函数还是先执行第二个 setTimeout 是不确定的。

    因此执行上面的例子,就可能出现两种情况了:

    1. 第一种情况 ajax 在 100 毫秒之内请求成功
    2. 第二中情况 ajax 在 100 毫秒之后请求成功

    对应的执行结果:

    1. // 第一种情况 ajax 在 100 毫秒之内请求成功的执行结果
    2. // d
    3. // c
    4. // a ajax 请求成功后的执行结果
    5. // b 100 毫秒延迟的 setTimeout 函数执行结果
    6. // 第二种情况 ajax 在 100 毫秒之后请求成功的执行结果
    7. // d
    8. // c
    9. // b
    10. // a