vlambda博客
学习文章列表

事件循环(Event Loop)机制

由于JavaScript是一门单线程的非阻塞的脚本语言。

单线程意味着JavaScript代码在执行的任何时候,都只有一个主线程来处理所有的任务。

而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件,网络请求)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候如果主线程空闲,则会去执行相应的回调。

1. JavaScript是单线程,非阻塞的

  • 单线程:DOM渲染和执行JavaScript代码时,都只有一个主线程来处理所有的任务。

  • 非阻塞: 当执行一项异步任务(无法立即返回结果,需要花费一定的时间,如AJAX请求, I/O事件)时,主线程挂起(pending)这个任务,然后在异步返回结果的时候再根据一定的规则去执行相应的回调。

2. 执行栈和事件队列

当javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆(heap)和栈(stack)中来加以区分。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。

  • 执行栈:JS代码执行时,全局代码会创建一个全局执行环境(什么是执行环境),函数被调用时会创建一个函数执行环境。由于JS是单线程,一次只能执行一个函数,于是这些函数在执行时就会按照先进后出的顺序添加到执行栈中。

  • 事件队列:异步代码执行时,不会等待它返回结果,而是将这个事件挂起,继续执行执行栈中的其他任务。当异步事件返回结果时,JS会将这个事件加入与当前执行栈不同的另外一个队列,即事件队列中。事件队列中的回调函数不会立即执行,而是等待当前执行栈中所有的任务执行完毕后,主线程处于空闲状态时,再执行回调函数中的同步代码。如此反复的过程,就是事件循环。

宏任务(macrotast)和微任务(microtask)

根据异步事件的类型,这个事件会被放到宏任务或微任务中去。在当前执行栈为空时,主线程会查看微任务队列中是否有事件。

  • 如果有,则依次执行全部微任务,如果微任务中产生了新的微任务,则继续执行微任务,直到所有微任务都执行完毕。然后再去宏任务队列中取出最前面的事件,然后执行其回调函数。

  • 如果没有,则执行宏任务中的事件

当前执行栈执行完毕时,然后会开始下一轮的事件循环,即执行微任务列中的全部事件,然后再执行下一个宏任务队列中的事件。

宏任务有:

  • script中的同步代码

  • setTimeout

  • setInterval

  • requestAnimationFrame

  • setImmediate(Node.js中)

  • UI Render

微任务有:

  • Promise

  • MutationObserve

  • Process.nextTick(Node.js中)

3. 实现一个函数,接收一个函数作为参数,然后反序执行

逻辑:在同步代码执行完后,主线程空闲状态,然后在执行异步回调。

let timer = null
const tasks = []
function fn(callback) {
if (timer) {
clearTimeout(timer)
}

tasks.unshift(callback)

timer = setTimeout(() => tasks.forEach(cb => cb()))
}

fn(() => console.log(1))
fn(() => console.log(2))
fn(() => console.log(3))

// 打印结果 321

及时获取更新,了解更多动态,请关注 https://www.gogoing.site