搜索
首页web前端js教程从setTimeout来谈谈说事件循环模型

  作为一个从其他编程语言(C#/Java)转到Javascript的开发人员,在学习Javascript过程中,setTimeout()方法的运行原理是我遇到的一个不太好理解的部分,本文尝试结合其他编程语言的实现,从setTimeout说事件循环模型

 

 1.从setTimeout说起

  setTimeout()方法不是ecmascript规范定义的内容,而是属于BOM提供的功能。查看w3school对setTimeout()方法的定义,setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

  语法setTimeout(fn,millisec),其中fn表示要执行的代码,可以是一个包含javascript代码的字符串,也可以是一个函数。第二个参数millisec是以毫秒表示的时间,表示fn需推迟多长时间执行。

  调用setTimeout()方法之后,该方法返回一个数字,这个数字是计划执行代码的唯一标识符,可以通过它来取消超时调用。

  起初我对 setTimeout()的使用比较简单,对其运行机理也没有深入的理解,直到看到下面代码

var start = new Date;
setTimeout(function(){
var end = new Date;
console.log('Time elapsed:', end - start, 'ms');
}, 500);
while (new Date - start < 1000) {};

  在我最初对setTimeout()的认识中,延时设置为500ms,所以输出应该为Time elapsed: 500 ms。因为在直观的理解中,Javascript执行引擎,在执行上述代码过程中,应当是一个由上往下的顺序执行过程,setTimeout函数是先于while语句执行的。可是实际上,上述代码运行多次后,输出至少是延迟了1000ms。

 2.Java对setTimeout的实现

  联想起以往学习Java的经验,上述Javascript的setTimeout()让我困惑。Java对setTimeout的实现有多种API实现,这里我们以java.util.Timer包为例。使用Timer在Java中实现上述逻辑,运行多次,输出都是Time elapsed: 501 ms。

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerTest {
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        long start = System.currentTimeMillis();
        Timer timer = new Timer();
        timer.schedule(new MyTask(start), 500);
        while (System.currentTimeMillis() - start < 1000) {};
    }
 
}
 
class MyTask extends TimerTask {
    private long t;
 
    public MyTask(long start) {
        // TODO Auto-generated constructor stub
        t=start;
    }
 
    @Override
    public void run() {
        // TODO Auto-generated method stub
        long end = System.currentTimeMillis();
        System.out.println("Time elapsed:"+(end - this.t)+ "ms");
    }
    
}

  这里深究setTimeout()为什么出现这一差异之前,先说说java.util.Timer的实现原理。

  上述代码几个关键要素为Timer、TimerTask类以及Timer类的schedule方法,通过阅读相关源码,可以了解其实现。

  Timer:一个Task任务的调度类,和TimerTask任务一样,是供用户使用的API类,通过schedule方法安排Task的执行计划。该类通过TaskQueue任务队列和TimerThread类完成Task的调度。

  TimerTask:实现Runnable接口,表明每一个任务均为一个独立的线程,通过run()方法提供用户定制自己任务。

  TimerThread:继承于Thread,是真正执行Task的类。

  TaskQueue:存储Task任务的数据结构,内部由一个最小堆实现,堆的每个成员为TimeTask,每个任务依靠TimerTask的 nextExecutionTime属性值进行排序,nextExecutionTime最小的任务在队列的最前端,从而能够现实最早执行。

 3.根据结果找原因

  看过了Java.util.Timer对类似setTimeout()的实现方案,继续回到前文Javascript的setTimeout()方法中,再来看看之前的输出为什么与预期不符。

var start = new Date;
setTimeout(function(){
var end = new Date;
console.log(&#39;Time elapsed:&#39;, end - start, &#39;ms&#39;);
}, 500);
while (new Date - start < 1000) {};

  通过阅读代码不难看出,setTimeout()方法执行在while()循环之前,它声明了“希望”在500ms之后执行一次匿名函数,这一声明,也即对匿名函数的注册,在setTimeout()方法执行后立即生效。代码最后一行的while循环会持续运行1000ms,通过setTimeout()方法注册的匿名函数输出的延迟时间总是大于1000ms,说明对这一匿名函数的实际调用被while()循环阻塞了,实际的调用在while()循环阻塞结束后才真正执行。

  而在Java.util.Timer中,对于定时任务的解决方案是通过多线程手段实现的,任务对象存储在任务队列,由专门的调度线程,在新的子线程中完成任务的执行。通过schedule()方法注册一个异步任务时,调度线程在子线程立即开始工作,主线程不会阻塞任务的运行。

  这就是Javascript与Java/C#之类语言的一大差异,即Javascript的单线程机制。在现有浏览器环境中,Javascript执行引擎是单线程的,主线程的语句和方法,会阻塞定时任务的运行,执行引擎只有在执行完主线程的语句后,定时任务才会实际执行,这期间的时间,可能大于注册任务时设置的延时时间。在这一点上,Javascript与Java/C#的机制很不同。

 4.事件循环模型

  在单线程的Javascript引擎中,setTimeout()是如何运行的呢,这里就要提到浏览器内核中的事件循环模型了。简单的讲,在Javascript执行引擎之外,有一个任务队列,当在代码中调用setTimeout()方法时,注册的延时方法会交由浏览器内核其他模块(以webkit为例,是webcore模块)处理,当延时方法到达触发条件,即到达设置的延时时间时,这一延时方法被添加至任务队列里。这一过程由浏览器内核其他模块处理,与执行引擎主线程独立,执行引擎在主线程方法执行完毕,到达空闲状态时,会从任务队列中顺序获取任务来执行,这一过程是一个不断循环的过程,称为事件循环模型。

  参考一个演讲中的资料,上述事件循环模型可以用下图描述。

  Javascript执行引擎的主线程运行的时候,产生堆(heap)和栈(stack)。程序中代码依次进入栈中等待执行,当调用setTimeout()方法时,即图中右侧WebAPIs方法时,浏览器内核相应模块开始延时方法的处理,当延时方法到达触发条件时,方法被添加到用于回调的任务队列,只要执行引擎栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些满足触发条件的回调函数。

  以演讲中的示例进一步说明

s1

s2

  以图中代码为例,执行引擎开始执行上述代码时,相当于先讲一个main()方法加入执行栈。继续往下开始console.log('Hi')时,log('Hi')方法入栈,console.log方法是一个webkit内核支持的普通方法,而不是前面图中WebAPIs涉及的方法,所以这里log('Hi')方法立即出栈被引擎执行。

s3

s4

  console.log('Hi')语句执行完成后,log()方法出栈执行,输出了Hi。引擎继续往下,将setTimeout(callback,5000)添加到执行栈。setTimeout()方法属于事件循环模型中WebAPIs中的方法,引擎在将setTimeout()方法出栈执行时,将延时执行的函数交给了相应模块,即图右方的timer模块来处理。

s5

  执行引擎将setTimeout出栈执行时,将延时处理方法交由了webkit timer模块处理,然后立即继续往下处理后面代码,于是将log('SJS')加入执行栈,接下来log('SJS')出栈执行,输出SJS。而执行引擎在执行万console.log('SJS')后,程序处理完毕,main()方法也出栈。

s6

s7

s8

  这时在在setTimeout方法执行5秒后,timer模块检测到延时处理方法到达触发条件,于是将延时处理方法加入任务队列。而此时执行引擎的执行栈为空,所以引擎开始轮询检查任务队列是否有任务需要被执行,就检查到已经到达执行条件的延时方法,于是将延时方法加入执行栈。引擎发现延时方法调用了log()方法,于是又将log()方法入栈。然后对执行栈依次出栈执行,输出there,清空执行栈。

  清空执行栈后,执行引擎会继续去轮询任务队列,检查是否还有任务可执行。

 5.webkit中timer的实现

  到这里已经可以彻底理解下面代码的执行流程,执行引擎先将setTimeout()方法入栈被执行,执行时将延时方法交给内核相应模块处理。引擎继续处理后面代码,while语句将引擎阻塞了1秒,而在这过程中,内核timer模块在0.5秒时已将延时方法添加到任务队列,在引擎执行栈清空后,引擎将延时方法入栈并处理,最终输出的时间超过预期设置的时间。

var start = new Date;
setTimeout(function(){
var end = new Date;
console.log(&#39;Time elapsed:&#39;, end - start, &#39;ms&#39;);
}, 500);
while (new Date - start < 1000) {};

  前面事件循环模型图中提到的WebAPIs部分,提到了DOM事件,AJAX调用和setTimeout方法,图中简单的把它们总结为WebAPIs,而且他们同样都把回调函数添加到任务队列等待引擎执行。这是一个简化的描述,实际上浏览器内核对DOM事件、AJAX调用和setTimeout方法都有相应的模块来处理,webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块,html的解析,css样式的计算等都由webcore实现。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现,这里还是继续以setTimeout为例,看下timer模块的实现。

  Timer类是webkit 内核的一个必需的基础组件,通过阅读源码可以全面理解其原理,本文对其简化,分析其执行流程。

webkittimer

  通过setTimeout()方法注册的延时方法,被传递给webcore组件timer模块处理。timer中关键类为TheadTimers类,其包含两个重要成员,TimerHeap任务队列和SharedTimer方法调度类。延时方法被封装为timer对象,存储在TimerHeap中。和Java.util.Timer任务队列一样,TimerHeap同样采用最小堆的数据结构,以nextFireTime作为关键字排序。SharedTimer作为TimerHeap调度类,在timer对象到达触发条件时,通过浏览器平台相关的接口,将延时方法添加到事件循环模型中提到的任务队列中。

  TimerHeap采用最小堆的数据结构,预期延时时间最小的任务最先被执行,同时,预期延时时间相同的两个任务,其执行顺序是按照注册的先后顺序执行。

var start = new Date;
setTimeout(function(){
console.log(&#39;fn1&#39;);
}, 20);
setTimeout(function(){
console.log(&#39;fn2&#39;);
}, 30);
setTimeout(function(){
console.log(&#39;another fn2&#39;);
}, 30);
setTimeout(function(){
console.log(&#39;fn3&#39;);
}, 10);
console.log(&#39;start while&#39;);
while (new Date - start < 1000) {};
console.log(&#39;end while&#39;);

  上述代码输出依次为

start while
end while
fn3
fn1
fn2
another fn2

 参考资料

  1.《Javascript异步编程》

  2.JavaScript 运行机制详解:再谈Event Loophttp://www.ruanyifeng.com/blog/2014/10/event-loop.html

  3.Philip Roberts: Help, I'm stuck in an event-loop.https://vimeo.com/96425312

  4.How JavaScript Timers Work.http://ejohn.org/blog/how-javascript-timers-work/

  5.How WebKit’s event model works.http://brrian.tumblr.com/post/13951629341/how-webkits-event-model-works

  6.Timer实现.http://blog.csdn.net/shunzi__1984/article/details/6193023

以上是从setTimeout来谈谈说事件循环模型的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
Python vs. JavaScript:开发人员的比较分析Python vs. JavaScript:开发人员的比较分析May 09, 2025 am 12:22 AM

Python和JavaScript的主要区别在于类型系统和应用场景。1.Python使用动态类型,适合科学计算和数据分析。2.JavaScript采用弱类型,广泛用于前端和全栈开发。两者在异步编程和性能优化上各有优势,选择时应根据项目需求决定。

Python vs. JavaScript:选择合适的工具Python vs. JavaScript:选择合适的工具May 08, 2025 am 12:10 AM

选择Python还是JavaScript取决于项目类型:1)数据科学和自动化任务选择Python;2)前端和全栈开发选择JavaScript。Python因其在数据处理和自动化方面的强大库而备受青睐,而JavaScript则因其在网页交互和全栈开发中的优势而不可或缺。

Python和JavaScript:了解每个的优势Python和JavaScript:了解每个的优势May 06, 2025 am 12:15 AM

Python和JavaScript各有优势,选择取决于项目需求和个人偏好。1.Python易学,语法简洁,适用于数据科学和后端开发,但执行速度较慢。2.JavaScript在前端开发中无处不在,异步编程能力强,Node.js使其适用于全栈开发,但语法可能复杂且易出错。

JavaScript的核心:它是在C还是C上构建的?JavaScript的核心:它是在C还是C上构建的?May 05, 2025 am 12:07 AM

javascriptisnotbuiltoncorc; saninterpretedlanguagethatrunsonenginesoftenwritteninc.1)javascriptwasdesignedAsalightweight,解释edganguageforwebbrowsers.2)Enginesevolvedfromsimpleterterterpretpreterterterpretertestojitcompilerers,典型地提示。

JavaScript应用程序:从前端到后端JavaScript应用程序:从前端到后端May 04, 2025 am 12:12 AM

JavaScript可用于前端和后端开发。前端通过DOM操作增强用户体验,后端通过Node.js处理服务器任务。1.前端示例:改变网页文本内容。2.后端示例:创建Node.js服务器。

Python vs. JavaScript:您应该学到哪种语言?Python vs. JavaScript:您应该学到哪种语言?May 03, 2025 am 12:10 AM

选择Python还是JavaScript应基于职业发展、学习曲线和生态系统:1)职业发展:Python适合数据科学和后端开发,JavaScript适合前端和全栈开发。2)学习曲线:Python语法简洁,适合初学者;JavaScript语法灵活。3)生态系统:Python有丰富的科学计算库,JavaScript有强大的前端框架。

JavaScript框架:为现代网络开发提供动力JavaScript框架:为现代网络开发提供动力May 02, 2025 am 12:04 AM

JavaScript框架的强大之处在于简化开发、提升用户体验和应用性能。选择框架时应考虑:1.项目规模和复杂度,2.团队经验,3.生态系统和社区支持。

JavaScript,C和浏览器之间的关系JavaScript,C和浏览器之间的关系May 01, 2025 am 12:06 AM

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!