美文网首页
javascript中的事件循环event-loop

javascript中的事件循环event-loop

作者: 焦迈奇 | 来源:发表于2019-04-10 20:58 被阅读0次

    线程

    javascript是单线程的语言,也就是说,同一个时间只能做一件事。而这个单线程的特性,与它的用途有关,作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质.
    [排队]

    单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着.

    var i, t = Date.now()
    for (i = 0; i < 100000000; i++) {}
    console.log(Date.now() - t) // 238
    

    像上面这样,如果排队是因为计算量大,CPU忙不过来,倒也算了
      但是,如果是网络请求就不合适。因为一个网络请求的资源什么时候返回是不可预知的,这种情况再排队等待就不明智了

    同步和异步

    同步 :如果在函数返回的时候,调用者就能够拿到结果,那么这个函数就是同步的.
    异步:如果函数在返回的时候,调用者还不能拿到预期结果,而是需要通过一定的方式拿到,则函数是异步的.

    正是由于JavaScript是单线程的,而异步容易实现非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,使用异步就成了必然的选择

    异步详解

    从上文可以看出,异步函数实际上很快就调用完成了。但是后面还有执行异步操作、通知主线程、主线程调用回调函数等很多步骤。我们把整个过程叫做异步过程。异步函数的调用在整个有异步过程中,只是一小部分.
    一个异步过程通常是这样的:主线程发起一个异步请求,异步任务接收请求并告知主线程已收到(异步函数返回);主线程可以继续执行后面的代码,同时异步操作开始执行;执行完成后通知主线程;主线程收到通知后,执行一定的动作(调用回调函数)

    因此,一个异步过程包括两个要素:注册函数和回调函数,其中注册函数用来发起异步过程,回调函数用来处理结果

    下面的代码中,其中的setTimeout就是异步过程的发起函数,fn是回调函数

    <pre style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px; list-style-type: none; list-style-image: none; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">setTimeout(fn, 1000);</pre>
    

    有一个很重要的问题,如何才算是异步操作执行完成呢?对于不同类型的异步任务,操作完成的标准不同

    【异步类型】

    一般而言,异步任务有以下三种类型

    1、普通事件,如click、resize等

    2、资源加载,如load、error等

    3、定时器,包括setInterval、setTimeout等

    下面对这三种类型分别举例说明,下面代码中,鼠标点击div时,就代表任务执行完成了

      console.log('click')
    }</pre>
    
      下面代码中,XHR对象的readyState值为4,即已经接收到全部响应数据了,代表任务执行完成
    
    [![复制代码](https://img.haomeiwen.com/i9540784/41e3e793a910b7dd.gif?imageMogr2/auto-orient/strip)](javascript:void(0); "复制代码") 
    
    <pre style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px; list-style-type: none; list-style-image: none; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ //实际操作
              result.innerHTML += xhr.responseText;
          }
      }
    }</pre>
    
    [![复制代码](https://img.haomeiwen.com/i9540784/2a2145079a30c7d8.gif?imageMogr2/auto-orient/strip)](javascript:void(0); "复制代码") 
    

    下面代码中,过1s后,代表任务执行完成

    <pre style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px; list-style-type: none; list-style-image: none; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">setTimeout(() => {
      console.log('timeout')
    },1000)</pre>
    

    对于同步任务来说,按顺序执行即可;但是,对于异步任务,各任务执行的时间长短不同,执行完成的时间点也不同,主线程如何调控异步任务呢?这就用到了消息队列
    消息对列
     有些文章把消息队列称为任务队列,或者叫事件队列,总之是和异步任务相关的队列

    可以确定的是,它是队列这种先入先出的数据结构,和排队是类似的,哪个异步操作完成的早,就排在前面。不论异步操作何时开始执行,只要异步操作执行完成,就可以到消息队列中排队

    这样,主线程在空闲的时候,就可以从消息队列中获取消息并执行

    消息队列中放的消息具体是什么东西?消息的具体结构当然跟具体的实现有关。但是为了简单起见,可以认为:消息就是注册异步任务时添加的回调函数。
    【栈】

    函数调用形成了一个栈帧

    [ 复制代码

    ](javascript:void(0); "复制代码")

    <pre style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px; list-style-type: none; list-style-image: none; font-family: "Courier New" !important; font-size: 12px !important;">function foo(b) {
    var a = 10;
    return a + b + 11;
    }
    function bar(x) {
    var y = 3;
    return foo(x * y);
    }
    console.log(bar(7));</pre>

    [ 复制代码

    ](javascript:void(0); "复制代码")

    当调用bar时,创建了第一个帧 ,帧中包含了bar的参数和局部变量。当bar调用foo时,第二个帧就被创建,并被压到第一个帧之上,帧中包含了foo的参数和局部变量。当foo返回时,最上层的帧就被弹出栈(剩下bar函数的调用帧 )。当bar返回的时候,栈就空了

    【堆】

    对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域

    【队列】

    一个 JavaScript 运行时包含了一个待处理的消息队列。每一个消息都与一个函数相关联。当栈拥有足够内存时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及因而创建了一个初始堆栈帧)。当栈再次为空的时候,也就意味着消息处理结束.
    事件循环
    下面来详细介绍事件循环。下图中,主线程运行的时候,产生堆和栈,栈中的代码调用各种外部API,异步操作执行完成后,就在消息队列中排队。只要栈中的代码执行完毕,主线程就会去读取消息队列,依次执行那些异步任务所对应的回调函数

    eventloop

    详细步骤如下:

    1、所有同步任务都在主线程上执行,形成一个执行栈

    2、主线程之外,还存在一个"消息队列"。只要异步操作执行完成,就到消息队列中排队

    3、一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取消息队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行

    4、主线程不断重复上面的第三步

    【循环】

    从代码执行顺序的角度来看,程序最开始是按代码顺序执行代码的,遇到同步任务,立刻执行;遇到异步任务,则只是调用异步函数发起异步请求。此时,异步任务开始执行异步操作,执行完成后到消息队列中排队。程序按照代码顺序执行完毕后,查询消息队列中是否有等待的消息。如果有,则按照次序从消息队列中把消息放到执行栈中执行。执行完毕后,再从消息队列中获取消息,再执行,不断重复。

    由于主线程不断的重复获得消息、执行消息、再取消息、再执行。所以,这种机制被称为事件循环

    【事件】

    为什么叫事件循环?而不叫任务循环或消息循环。究其原因是消息队列中的每条消息实际上都对应着一个事件

    DOM操作对应的是DOM事件,资源加载操作对应的是加载事件,而定时器操作可以看做对应一个“时间到了”的事件

    实例

    下面以一个实例来解释事件循环机制

    <pre style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px; list-style-type: none; list-style-image: none; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">console.log(1)
    div.onclick = () => {console.log('click')}
    console.log(2)
    setTimeout(() => {console.log('timeout')},1000)</pre>
    

    1、执行第一行代码,第一行是一个同步任务,控制台显示1
      2、执行第二行代码,第二行是一个异步任务,发起异步请求,可以在任意时刻执行鼠标点击的异步操作
      3、执行第三行代码,第三行是一个同步任务,控制台显示2
      4、执行第四行代码,第四行是一个异步任务,发起异步请求,1s后执行定时器任务
      5、假设从执行第四行代码的1s内,执行了鼠标点击,则鼠标任务在消息队列中排到首位
      6、从执行第四行代码1s后,定时器任务到消息队列中排到第二位
      7、现在同步任务已经执行完毕,则从消息队列中按照次序把异步任务放到执行栈中执行
      8、则控制台依次显示'click‘、'timeout'
      9、过了一段时间后,又执行了一次鼠标点击,由于消息队列中已经空了,则鼠标任务在消息队列中排到首位
      10、同步任务执行完毕后,再从消息队列中按照次序把异步任务放到执行栈中执行
      11、 则控制台显示'click'

    【异步过程】

    下面以一个实例来解释一次完整的异步过程

    <pre style="margin: 0px; white-space: pre-wrap; word-wrap: break-word; padding: 0px; list-style-type: none; list-style-image: none; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">div.onclick = function fn(){console.log('click')}</pre>
    

    1、主线程通过调用异步函数div.onclick发起异步请求
    2、在某一时刻,执行异步操作,即鼠标点击
    3、接着,回调函数fn到消息队列中排队
    4、主线程从消息队列中读取fn到执行栈中
    5、然后在执行栈中执行fn里面的代码console.log('click')
    6、于是,控制台显示'click'

    同步变异步

    每一个消息完整的执行后,其它消息才会被执行。这点提供了一些优秀的特性,包括每当一个函数运行时,它就不能被抢占,并且在其他代码运行之前完全运行

    这个模型的一个缺点在于当一个消息需要太长时间才能完成,Web应用无法处理用户的交互,例如点击或滚动

    于是,对于这种情况的常见优化是同步变异步

    https://www.cnblogs.com/xiaohuochai/p/8527618.html

    相关文章

      网友评论

          本文标题:javascript中的事件循环event-loop

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