一、事件循环

JavaScript是单线程,同一个时间只能做一件事情,所以执行任务需要排队。如果前一个耗时很长,那么下一个只能等待。

1)两种任务

为了更好的处理任务,JavaScript语言的设计者将任务分为两种:同步任务(synchronous)与异步任务(asynchronous)。

同步任务:在主线程上排队执行的任务。

异步任务:放在“任务队列”(task queue)中,只有当主线程空了,才会将“任务队列”中的任务放到主线程中。

这就是JavaScript的运行机制,这个过程会不断重复,这个机制叫事件循环(Event Loop)。

2)事件循环

事件循环模型可以用下图描述,图片来自Philip Roberts的演讲《Help, I’m stuck in an event loop》:

1. “WebAPIs”内的就是异步任务,包括DOM事件、Ajax和setTimeout。

2. “callback queue”内的是一个任务队列,包括click、load、done。

3. “stack”内的就是同步任务,只有当“stack”内的清空后,才会去轮询任务队列。

下面是一段代码说明,图片中的内容是打印结果,没什么悬念。

console.log('Hi');
setTimeout(function() {
console.log('there');
},5000);
console.log('SJS');

1. 将log('Hi')方法入栈,这是个普通方法,出栈被引擎执行,输出“Hi”。

2. 将setTimeout方法入栈,这是个WebAPIs内的方法,出栈被引擎交给了相应模块,继续处理后面代码。

3. 将log('SJS')加入执行栈,出栈输出“SJS”。

4. 在setTimeout方法执行5秒后,到达触发条件,将setTimeout加到任务队列中。

5. 此时的执行栈为空,所以引擎开始轮询检查任务队列,有个setTimeout,于是将setTimeout加入执行栈中。

6. 在setTimeout中有个log('there')方法,将此方法入栈,输出“there”。

3)循环过程去取Ajax

下图展示的是主线程通过事件循环过程去取Ajax的消息:

二、定时器

定时器就是setTimeout(fn, delay)setInterval(fn, delay),定时器设定的延时是没有保证的。

如果setTimeout在时间点“n”被调用,那么执行定时器代码的JavaScript任务会在“n+delay”后才加入到消息队列中。

下图是JQuery的作者John Resig画的一张示例图:

左边是运行时间(单位ms),中间是JavaScript代码段,右边是代码计划开始执行时间

1. 0ms时JavaScript开始执行,2ms启动setTimeout,6ms加入Mouse Click,10ms启动setInterval,12ms加入setTimeout,20ms加入setInterval,30ms、40ms、50ms加入setInterval。

2. 第1段JavaScript执行了大概18ms,在18ms时,setTimeout过期了。

3. 按照单线程FIFO规则,接下来执行Mouse Click,再依次运行setTimeout和setInterval。

4. 第1个setInterval(20ms加入)还在排队等候中,30ms又要加入setInterval,但浏览器只让一个setInterval排队,其它的都废弃掉。

5. 第1个setInterval在36ms时开始执行,此程序需要执行6ms,第2个setInterval在40ms时开始排队,42ms时开始执行。

6. 第2个执行的setInterval在48ms时完成执行,50ms时第3个setInterval开始执行,不需要排队,直接运行。

三、分割任务

在JavaScript执行的时候,页面渲染的所有更新操作都要暂停。在执行繁忙的时候,可能会导致浏览器很卡或似乎要挂掉。

定时器,可以有效暂停一段JavaScript代码的执行,还可以将代码的各个部分,分解成不会让浏览器挂掉的碎片

1)未优化代码

长时间运行的任务,用手机扫二维码看效果会更明显

var tbody = document.getElementsByTagName("tbody")[0];
for (var i = 0; i < 20000; i++) { //创建20000个tr
var tr = document.createElement("tr");
for (var t = 0; t < 6; t++) { //每个tr有6个td
var td = document.createElement("td");
td.appendChild(document.createTextNode(i + "," + t));
tr.appendChild(td);
}
tbody.appendChild(tr); //将tr添加到tbody中
}

2)已优化代码

利用定时器分解任务,将强循环转化为非阻塞操作:

//配置部分
var rowCount = 20000;
var divideInto = 4;
var chunkSize = rowCount / divideInto; //将20000分成4个部分
var iteration = 0; var table = document.getElementsByTagName("tbody")[0]; setTimeout(function generateRows() {
var base = chunkSize * iteration; //计算上次中断的地方
//添加tr部分
for (var i = 0; i < chunkSize; i++) {
var tr = document.createElement("tr");
for (var t = 0; t < 6; t++) {
var td = document.createElement("td");
td.appendChild(document.createTextNode((i + base) + "," + t + "," + iteration));
tr.appendChild(td);
}
table.appendChild(tr);
}
iteration++; //调度下一阶段
if (iteration < divideInto)
setTimeout(generateRows, 0);
}, 0);

参考资料:

JavaScript的计时器的工作原理

JavaScript:彻底理解同步、异步和事件循环(Event Loop)

从setTimeout说事件循环模型

JavaScript 运行机制详解:再谈Event Loop

JavaScript忍者秘籍

一家之言:说说 JavaScript 计时器的工作原理

JavaScript定时器分析的更多相关文章

  1. 一些有用的javascript实例分析(三)

    原文:一些有用的javascript实例分析(三) 10 输入两个数字,比较大小 window.onload = function () { var aInput = document.getElem ...

  2. 关于JavaScript定时器我的一些小理解

    因为自己在平时工作中,有些功能需要用到定时器,但是定时器并不像我们表边上看到的那样,所以这周末我看看书查查资料,深入研究了一下JavaScript中的定时器,那么废话不多说,下面进入我们今天的正题. ...

  3. Javascript 定时器的使用陷阱 (setInterval)

    setTimeout(function(){ // 其他代码 setTimeout(arguments.callee, interval); }, interval); setInterval会产生回 ...

  4. Javascript定时器(三)——setTimeout(func, 0)

    setTimeout(func, 0)可以使用在很多地方,拆分循环.模拟事件捕获.页面渲染等 一.setTimeout中的delay参数为0,并不是指马上执行 <script type=&quo ...

  5. Javascript定时器(二)——setTimeout与setInterval

    一.解释说明 1.概述 setTimeout:在指定的延迟时间之后调用一个函数或者执行一个代码片段 setInterval:周期性地调用一个函数(function)或者执行一段代码. 2.语法 set ...

  6. [转载]JavaScript内存分析

    https://github.com/CN-Chrome-DevTools/CN-Chrome-DevTools/blob/master/md/Performance-Profiling/javasc ...

  7. JavaScript 性能分析新工具 OneProfile

    OneProfile 是一个网页版的小工具,可以用全新的方式展示 JavaScript 性能分析的结果,帮助开发者洞悉函数调用关系,优化应用性能. 点击打开 OneProfile 背景 Chrome ...

  8. 一些有用的javascript实例分析(一)

    原文:一些有用的javascript实例分析(一) 本文以http://fgm.cc/learn/链接的实例索引为基础,可参见其实际效果.分析和整理了一些有用的javascript实例,相信对一些初学 ...

  9. 一些有用的javascript实例分析(二)

    原文:一些有用的javascript实例分析(二) 5 求出数组中所有数字的和 window.onload = function () { var oBtn = document.getElement ...

随机推荐

  1. 一次MySQL死锁问题解决

    一次MySQL死锁问题解决 一.环境 CentOS, MySQL 5.6.21-70, JPA 问题场景:系统有定时批量更新数据状态操作,每次更新上千条记录,表中总记录数约为500W左右. 二.错误日 ...

  2. 基于Hadoop的改进Apriori算法

    一.Apriori算法性质 性质一: 候选的k元组集合Ck中,任意k-1个项组成的集合都来自于Lk. 性质二: 若k维数据项目集X={i1,i2,-,ik}中至少存在一个j∈X,使得|L(k-1)(j ...

  3. java数组、泛型、集合在多态中的使用及对比

    我们在使用数组,泛型集合的过程中不可避免的会碰到多态,或者说什么情况下能如何使用父数组引用子数组(集合.泛型)呢? 数组在多态中的使用 元素为父类型的数组引用可指向元素为子类型的数组对象 当数组被调用 ...

  4. 裸机(Bare Metal)安装CoreOS

    本文以 1235.9.0-stable 为例,在WMWare中进行安装. 1.获取安装资源 https://coreos.com/releases/ 通过"Browse Images&quo ...

  5. CentOs下安装PHP环境的步骤

    前言 在CentOs环境下安装php开发环境,需要首先安装一些源文件,然后使用yum命令直接安装即可,在Fedora 20 源中已经有了PHP的源,直接可以使用以下命令安装即可: # yum inst ...

  6. 偶尔发生File has been moved - cannot be read again,其实是个误解

    使用poi上传.xlsx文件时,出现如下错误 Exception in thread "pool-3-thread-2" java.lang.IllegalStateExcepti ...

  7. 算法模板——Dinic网络最大流 2

    实现功能:同Dinic网络最大流 1 这个新的想法源于Dinic费用流算法... 在费用流算法里面,每次处理一条最短路,是通过spfa的过程中就记录下来,然后顺藤摸瓜处理一路 于是在这个里面我的最大流 ...

  8. Maven工具的使用总结

    Maven作为构建工具,现在项目开发使用的越来越多,相对于ant和gradle,我更喜欢使用maven,先总结maven使用的一系列知识: 一.maven相关的网址: 名称 地址 其他 maven官网 ...

  9. 笔记本win10关机异常解决

    自从使用了win10 以后,小编已经情不自禁的爱上了她——迄今为止最NB的windows系统 但令人头疼的问题也随之而来,前几天购置了一款三星的SSD固态硬盘,马上就装了win10,某天晚上关机以后发 ...

  10. 如何javascript获取css中的样式

    obj.style.height只能获取行间样式,但是我们要怎么获取写在css文件中的样式呢? 首先我们要用一个新的方法currentStyle.这个方法由current和style两个单词组成意思是 ...