Event loop

浏览器中的 Event loop

event loop

JavaScript 是单线程的

       首先,语言产生的时代多进程多线程的架构并不普及,基于当时硬件支持也不好,而且 多线程比较复杂,多线程操作需要加锁,使得编码方面就会变得很复杂;而且当时 JavaScript 只是处理页面的用户交互,以及操作 DOM 和 CSS,如果是多线程,两个线程 同时操作 同一个 DOM, 就会产生 预期的结果,所以 JavaScript 选择单线程。

基本概念

    JavaScript 分为 同步任务 异步任务

    所有同步任务都在  main thread 主线程 上执行,形成一个 执行栈(execution context stack),所有的任务都会被放到执行栈等待主线程执行;

    执行栈 采用的是 后进先出 的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空;

   主线程之外,存在一个任务队列(task queue),在走主流程的时候,如果碰到异步任务,那么就在 任务队列 中放置这个异步任务;

    一旦 执行栈 中所有 同步任务执行完毕,系统就会读取 任务队列,看看里面存在哪些事件。那些对应的异步任务 在异步任务有了结果后,将注册的回调函数放入 任务队列 中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

   任务队列 Task Queue,即队列,是一种先进先出的一种数据结构。


宏任务和微任务

       在 JavaScript 中,任务被分为两种,一种 宏任务(MacroTask),一种叫 微任务(MicroTask)。

       MacroTask(宏任务)有:setTimeout、setInterval、setImmediate、I/O、UI Rendering

       MicroTask(微任务)有:Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver

       微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。

       宏任务结束后,会执行渲染,然后执行下一个宏任务, 而微任务可以理解成在当前宏任务执行后立即执行的任务。

        宏任务 --> 微任务 --> 渲染 --> 宏任务 --> ......

         微任务就是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。 

        当JavaScript执⾏⼀段脚本的时候,V8会为其创建⼀个全局执行上下文,在创建全局执行上下文的 同时,V8引擎也会在内部创建⼀个 微任务队列 来存放微任务;

        在当前宏任务执行的过程中,有时候会产生多个微任务;如果在执行微任务的过程中,产⽣了新的微任务,同样会将该微任务添加到微任务队列中,V8引擎⼀直循环执行微任务队列中的任务,直到队列为空才算执行结束,并不会推迟到下个宏任务中执行。

       总结 :微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。


浏览器事件循环的进程模型

    执行栈在执行完 同步任务 后,查看 执行栈 是否为空,如果执行栈为空,就会去检查 微任务 (microTask)队列是否为空,如果为空的话,就执行 Task(宏任务),否则就一次性执行完所有微任务。

    每次单个宏任务 执行完毕后,检查 微任务 (microTask) 队列是否为空,如果不为空的话,会按照 先入先出 的规则全部执行完微任务(microTask)后,设置 微任务(microTask)队列为null,然后再执行 宏任务,如此循环。

    注意 :async/await 在底层转换成了 promise 和 then 回调函数,也就是说,这是 promise 的语法糖。

    sync函数在await之前的代码都是同步执行的,可以理解为await之前的代码属于new Promise时传入的代码,await之后的所有代码都是在Promise.then中的回调

    总结

    每当一个 js 脚本运行的时候,都会先执行script中的整体代码;

    当执行栈中的同步任务执行完毕,就会执行 微任务 中的第一个任务并推入执行栈执行,当执行栈为空,则再次读取执行微任务,循环重复直到微任务列表为空;

    等到微任务列表为空,才会读取宏任务中的第一个任务并推入执行栈执行,当执行栈为空则再读取执行微任务,微任务为空才再读取执行宏任务,如此循环。


Nodejs 中的 Event Loop

       Node 中的 Event Loop 和浏览器中的是完全不相同的东西

Event Loop

    nodejs 的 event loop 分为 6 个阶段,每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。

        timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调

        I/O callbacks 阶段:执行一些系统调用错误,比如网络通信的错误回调

        idle, prepare 阶段:仅node内部使用

        poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里

        check 阶段:执行 setImmediate() 的回调

        close callbacks 阶段:执行 socket 的 close 事件回调

    执行顺序

        一开始执行栈的同步任务(宏任务)执行完毕后(将异步任务放入对应的队列),再会先去执行微任务(这点跟浏览器端的一样, 这个过程中 先执行 process.nextTick, 在执行其他 微任务,process.nextTick 优先级较高),接着进入 event loop 的 六个阶段,循环执行。

        详细的可以参看文档 事件循环

    process.nextTick(微任务)

        这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行


    浏览器 Event loop 和 nodejs Event loop 差异

        浏览器环境下,microtask的任务队列是每个macrotask执行完之后执行。

网图

        在Node.js中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。

网图

你可能感兴趣的:(Event loop)