leyu乐鱼






      leyu乐鱼

      浅(qiǎn)谈javascript事件循环机制

      发布于: 2018-07-06    浏览: 2274    作者:Wu


      事件(jiàn)的处(chù)理


      浏览器是一个事件驱动(dòng)(event-driven)架构的软件。它的UI线程中会不断产生用户事件。但是(shì)处理(lǐ)事(shì)件的JavaScript是单线程执行的,这是(shì)一(yī)个浏览器环境下难以改变的(de)现状(HTML5 Web Works没有(yǒu)从本质上改(gǎi)变这个模型)。这意味着:在JavaScript处理某个任(rèn)务(执行某段(duàn)代码)过(guò)程中,如果产生了用户事件,它不会(huì)立即被处理。那这种情(qíng)况该怎么(me)办呢(ne)?



      浏览器维护了(le)一个“任务(wù)队列”(一个优先队列数据(jù)结构),它是一个(gè)浏览器进程(chéng)资源(yuán)。每当UI线程产生一个事件,事件对象(xiàng)就被(bèi)当做任务放入任务队列(liè)中(enqueue)。当JavaScript执行线程空闲的时候,队列(liè)中的一个任(rèn)务就会被送往JavaScript执行线程(dequeue),进行相应的处理。
      这个enqueue和dequeue的机制就是“Event Loop”。

      但是,不仅(jǐn)用户(hù)事件可以被Event Loop机(jī)制处(chù)理,还能更多的东西是依赖(lài)这个机制的。


      异步IO的(de)处理


      如果(guǒ)没有异步的理念,这个世界(jiè)会完全不同:一(yī)个耗时的I/O操作(例如HTTP请求(qiú))会导(dǎo)致JavaScript执行线程(chéng)等待(dài),而(ér)后续(xù)的操作得不到执行。这种(zhǒng)情况下,一个耗时的服务器端数据库操作http请求,会让(ràng)JavaScript执行线(xiàn)程(chéng)阻(zǔ)塞,浏(liú)览器(qì)将长期(qī)处(chù)于假死状(zhuàng)态,在此(cǐ)期间,其(qí)他(tā)后续操作(包括(kuò)用户的交互事件(jiàn))得(dé)不到响应。

      好在浏(liú)览器(qì)不是单线程的。它(tā)可(kě)以(但(dàn)不是必(bì)须)让这些I/O任务让其他线程(chéng)来托(tuō)管(guǎn),这样就形成了一个执(zhí)行(háng)任务的线程池。但是这些任务的(de)结果总归(guī)要回(huí)到JavaScript执行线程上处理(lǐ),于是这些任务也被放到任务队列中:需要被托管的任务被放(fàng)入队列中(enqueue),已完成的任务会被从(cóng)队(duì)列中(zhōng)一个(gè)个取出(dequeue),回(huí)到JavaScript执行线程执行回调。在(zài)这(zhè)些耗时的I/O任务(wù)被托管的时候,JavaScript执行(háng)线程可以执行(háng)其他代码。

      在(zài)Node中,这个过程是(shì)类似的。本(běn)文不表。

      这便(biàn)是异步的原理了。我们看到(dào)它同样(yàng)依赖Event Loop的机制。


      定时器(qì)


      浏(liú)览器的全局对象window提(tí)供了(le)两个方法,setTimeout和setInterval。这两个(gè)方法其实是调用了(le)浏览器的API,将一个任务移除(chú)出JavaScript执行线程中,延时处理。

      我们现(xiàn)在马上可(kě)以反应过来:这个将要(yào)被延时的任(rèn)务同(tóng)样是放到(dào)了任务队列中。在一次Event Loop过程中,它会(huì)优先将该时间点下已经到时的延时任务移除出队列(liè),放入(rù)JavaScript执行线程中。这(zhè)意味着,任(rèn)务队(duì)列是一个优(yōu)先队(duì)列。

      但是由于JavaScript执行线程的执行时间是不确定的,所(suǒ)以这个(gè)延(yán)时只(zhī)是一个大体的值(zhí),它取决于JavaScript执行线程的(de)执(zhí)行时间。


      回调函数


      任务完成的时(shí)候,JavaScript需要执行(háng)哪段代(dài)码来处理呢(ne)?当(dāng)然是回调函(hán)数了(le)。

      但是不免奇怪的一点就是:JavaScript中怎么知道要执行(háng)的是哪个回调函数呢(ne)?答(dá)案就是:任(rèn)务被放入任务(wù)队列的时候,该任务的回调函数(shù)会(huì)被注册(注册到什么地方?需要进一步(bù)探(tàn)究)。这样,当特定任务完成的时候,任务结果和回调标记会返回给JavaScript执行线程(chéng),进入执(zhí)行栈。


      事(shì)件处理器(qì)


      与其他任务不(bú)同,事件并不是(shì)由JavaScript执行线(xiàn)程发出(chū)的,而是从UI线程(chéng)中发(fā)出的。

      事(shì)件(jiàn)处理器和回调函(hán)数类似。但是特定的(de)事件处理(lǐ)器在浏览器进入异步事件(jiàn)驱动(dòng)阶段时(shí)就会(huì)针(zhēn)对特定的事件注(zhù)册。当事件对象返回到JavaScript执行线程时,事件处理器也会同时进入执行(háng)栈中执行。


       ——本文并(bìng)非原创,如有侵权请联系管(guǎn)理员删除。

      在(zài)线客服(fú)

      售前咨询

      售(shòu)后(hòu)服务

      投(tóu)诉/建议

      服务热(rè)线
      0731-83091505
      18874148081

      leyu乐鱼

      leyu乐鱼