美文网首页
浏览器环境和Node环境下事件循环的区别

浏览器环境和Node环境下事件循环的区别

作者: Allan要做活神仙 | 来源:发表于2019-03-06 15:10 被阅读0次

    2019-04-23-10:19:不能停,继续~
    今天补充下Node中的事件循环,它与浏览器的事件循环不同。

    微任务执行时机不同

    Node环境:微任务在事件循环的各个阶段的 空隙(中间)执行
    浏览器:微任务在事件循环的宏任务执行完后执行

    <------microTasks
        ┌───────────────────────┐
    ┌─>│        timers         │
    │  └──────────┬────────────┘
    |             |            <----- microTasks
    │  ┌──────────┴────────────┐
    │  │     I/O callbacks     │
    │  └──────────┬────────────┘
    |             |            <----- microTasks
    │  ┌──────────┴────────────┐
    │  │     idle, prepare     │
    │  └──────────┬────────────┘ 
    |             |            <----- microTasks
    |             |
    |             |                   ┌───────────────┐
    │  ┌──────────┴────────────┐      │   incoming:   │
    │  │         poll          │<─────┤  connections, │
    │  └──────────┬────────────┘      │   data, etc.  │
    |             |                   └───────────────┘
    |             |            <----- microTasks
    │  ┌──────────┴────────────┐      
    │  │        check          │
    │  └──────────┬────────────┘
    |             |            <----- microTasks
    │  ┌──────────┴────────────┐
    └──┤    close callbacks    │
        └───────────────────────┘
    

    Node采用的是单线程的处理机制(所有的I/O请求都采用非阻塞的工作方式),至少从Node.js开发者的角度是这样的。 而在底层,Node.js借助libuv来作为抽象封装层, 从而屏蔽不同操作系统的差异,Node可以借助livuv来来实现多线程。下图表示了Node和libuv的关系。

    Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给V8引擎。可以简单用下面这张图来表示。

    每一个I/O都需要一个回调函数——一旦执行完便推到事件循环上用于执行。

    2019-03-06-12:49:不能停,继续~

    Event Loop

    主线程从 ”任务队列” 中读取事件,这个过程是循环不断的,所以整个的这种 运行机制 又称为Event Loop(事件循环)。

    只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会不断重复。

    所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

    具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

    (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
    (2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
    (3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

    (4)主线程不断重复上面的第三步。

    JS有两种异步:
    一种是基于浏览器的异步 IO,比如 Ajax
    另一种就是基于 setTimeoutsetInterval

    浏览器异步 IO,比如 ajax ,写代码都是顺序执行的,但在真正处理请求的时候有一个单独的浏览器线程来处理,处理完后触发回调。

    JS 中有事件队列,Javascript 主线程就是 轮询 这个队列处理。这个好处是使得 CPU 永远是忙碌状态。(机制:消息(事件)驱动)

    对于setTimeoutsetInterval来说,当js线程执行到该代码片段时,js主线程会根据所设定的时间,当设定的时间到期时,将设置的回调函数放到事件队列中,然后js主线程继续去执行下边的代码,当js线程执行完主线程上的代码之后,会去循环执行事件队列中的函数。至于setTimeout或者setInterval设定的执行时间在实际表现时会有一些偏差,普遍的一个解释为,当定时任务的时间到期时,本应该去执行该回调函数,但是这时js主线程可能还有任务在执行,或者是该回调函数再事件队列中排的比较靠后,就导致该回调函数执行时间与定时器设定时间不一致。

    关键字:主线程(会形成执行栈)、队列任务“主完异进”

    其它相关

    js本身执行是单线程的,也就是说当前代码执行的时候,是会阻塞其他代码执行的。
    但是js的运行环境,譬如浏览器本身是多线程执行的,包括javascript引擎线程,界面渲染线程,浏览器事件触发线程,Http请求线程等。

    一道经典的前端面试题

    for(var i = 0; i < 5; i ++) {
        setTimeout(function() {
            console.log(i);
        }, 0);
    }
    

    输出5个5,为什么呢?

    因为setTimeout的任务是异步的,js执行栈(JS引擎中负责解释和执行JavaScript代码的线程,可以成为主线程)在执行完js代码后,才会去从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。
    所以上面的代码中,for循环执行完毕之后,setTimeout里头的回调函数才会被调用。那个时候i已经变成了5,因为放入了5个定时器,所以会输出5个5。

    js执行栈

    JavaScript是单线程执行的,无法同时执行多段代码。当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列。一旦当前任务执行完毕,再从队列中取出下一个任务,这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是Ajax请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript线程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

    也就是说setTimeout只能保证在指定的时间过后将任务(需要执行的函数)插入队列等候,并不保证这个任务在什么时候执行。执行javascript的线程会在空闲的时候,自行从队列中取出任务然后执行它。javascript通过这种队列机制,给我们制造一个异步执行的假象。

    部分引用来自:https://blog.csdn.net/qq_36995542/article/details/80007381

    相关文章

      网友评论

          本文标题:浏览器环境和Node环境下事件循环的区别

          本文链接:https://www.haomeiwen.com/subject/rtetpqtx.html