教你如何十分钟掌握事件循环机制

118 阅读4分钟

事件循环机制(EventLoop)

小伙伴们大家好,今天分享一篇关于事件循环机制的文章,事件循环可以说是面试中的必问问题了, 了解事件循环可以帮助我们更深刻的理解JS执行逻辑,那么话不多说,来看看事件JS的事件循环到底是什么样的吧.

JS为什么是单线程?

首先为什么会有事件循环? 因为JS是单线程的, 那JS为什么是单线程呢, 明明其他语言都可以做到多线程运行为什么JS不行?

其实JS是在浏览器中运行渲染的, 由于浏览器是多线程的, JS如果也是多线程, 那么在浏览器运行当前JS时创建了两个线程 process1 和 procress2, 此时JS对同时对一个DOM元素进行操作, 一个线程创建DOM,一个线程删除DOM, 这两个命令矛盾了, 此时浏览器该如何执行呢? 所以JS被设计成了单线程, 所以才有了事件循环机制

单线程的JS为什么需要异步

如果JS中不存在异步, 只能自上至下执行, 那么如果一行代码执行了很长的时间, 下面的代码就会被阻塞, 这样就导致用户会有一个很差的使用体验, 所以JS中存在异步执行

什么是事件循环?

事件循环我们可以理解为是JavaScript或者Node为了解决单线程执行代码不阻塞主进程的一种机制,也就是JavaScript的异步原理

什么是任务队列?

JavaScript语言将任务分为两种模式执行分别为同步任务和异步任务,当JavaScript代码执行时, 同步和异步任务会进入到不同的场所等待执行,同步任务会进入到主线程中, 异步任务则会进入到Event Table并注册回调函数, 当满足了触发条件之后在被推入Event Queue, 主线程中的任务先执行,当主线程任务全部执行完毕后, 执行栈的任务为空时, 就会读取任务队列(Event Queue)中的事件, 去执行对应的回调函数.

7790d90ef901b92971b0e8c47d92834.png

宏任务、微任务

在异步任务中,又分为宏任务和微任务, 他们都属于耗时任务

宏任务微任务
整体的script代码Promise(then. catch, finally...)
计时器(setTimeout, setInterval)async, await
AJAX请求Object.observe(用来实时监测JS中对象的变化)
i/o操作(输入输出, 比如读取文件操作、网路请求)MutationObserver(监听DOM树变化)
ui render(dom渲染)process.nextTick

异步任务执行顺序

上面提到了js代码执行时会先执行同步代码,同步代码执行完执行异步,那异步代码的执行顺序是什么呢? 首先异步任务中,遇到异步宏任务放到宏任务队列中,遇到微任务则放到微任务队列, 当所有同步代码执行完毕后,将所有微任务从任务队列推到执行栈中执行,所有微任务执行完毕后在将异步宏任务调入到执行栈执行,一直循环至所有的任务执行完毕(完成一次事件循环Event Loop)

案例练习

setTimeout(function () {
    console.log('timeout1')
}, 1000)

console.log('start')

Promise.resolve().then(function () {
    console.log('promise1')
    
    Promise.resolve().then(function () {
        console.log('promise2')
    })
    
    setTimeout(function () {
        Primise.resolve().then(function () {
            console.log('promise3')
        })
        
        console.log('timeout2')
    }, 10)
})

console.log('done')

解析:

  1. 按顺序执行, setTImeout宏任务, 放到宏任务队列, 记为time1
  2. console.log('start')为同步任务直接在执行栈中执行,输出start
  3. Promise.then为微任务, 进入微任务队列
  4. 按顺序执行,执行同步任务console.log('done'), 输出done
  5. 同步任务全部执行完毕, 执行异步任务, 先执行异步微任务, 在Promise中继续按照有同步先执行同步没有同步任务执行微任务的顺序执行, 所以先输出promise1, 然后执行promise.then(),输出promise2, 最终执行宏任务setTimeout, 先输出同步任务timeout2,在输出promise3
  6. 最后执行之前标记为time1的计时器,输出timeout1

输出结果: start, done, promise1, promise2, timeout2, promise3, timeout1

async function async1 () {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}

async function async2 () {
    console.log('async2')
}

console.log('script start')

setTimeout(function () {
    console.log('setTimeout')
}, 0)

async1()

new Promise(resolve => {
    console.log('promise0')
    resolve()
})
.then(() => {
    console.log('promise1')
})
.then(() => {
    console.log('promise2')
})

console.log('script end')

解析:

  1. 按顺序执行, anync1 async2都是微任务进入到微任务队列, 执行同步任务输出script start
  2. 计时器记为time1, 执行async1()
  3. 函数async1执行, 输出async1 start, 然后执行await async2(), 输出async2, 因为await等待一个成功的promise对象, async2中没有返回成功的promise对象, 所以会阻塞await下面的代码执行
  4. 继续向下执行, 输出promise0, 以及script end
  5. 执行promise.then中的任务输出 promise1, promise2
  6. 微任务全部执行完毕后执行async1中未执行完的代码输出async1 end
  7. 最后执行time1, 输出setTimeout

输出结果: script start, async1 start, async2, promise0, script end, promise1, promise2, async1 end, setTimeout