JavaScript定时器及其他
By Abyssly
Jun 20 2014 Updated:Jun 20 2014
平时工作中不可避免地要嵌套网页,对JavaScript的深入了解还是很有必要滴。而JavaScript中一个容易让人迷惑的地方就是定时器了,恐怕我们每天都在用,但我们真的足够理解吗?反正我之前只是随便用用,最近拜读了一些资料,感觉还是收获不少,在此作一个归纳。
最重要的概念
JavaScript引擎是单线程的。HTML5引入了Web Workers的特性,会从一定程度上突破这个限制。但话说回来,我们还是需要面对现实,认清国情。
单线程意味着,JavaScript代码并不会像线程一样被切来切去,更不会有两段代码同时执行,在同一个时刻始终只能有一段代码得到执行,各段代码依次执行下去。我们脑海中要时刻有这个概念。
定时器怎么创建
定时器并非JavaScript语言本身的特性,而是浏览器提供给我们的一个特性,它的作用是允许我们异步地将一段代码推迟一定毫秒后执行。
先看怎么用,具体来说,浏览器给了我们两个全局的方法去创建定时器:
- id = setTimeout(fn, delay) 初始化一个定时器,它将在指定的delay毫秒后执行传入的fn回调函数。返回一个唯一标识这个定时器的值。
- id = setInterval(fn, delay) 初始化一个定时器,每隔deplay毫秒就执行传入的fn回调函数。返回一个唯一标识这个定时器的值。
相应的有两个取消定时器的方法:clearTimeout(id)和clearInterval(id),作用是一样的,都是取消标识为id的定时器,事实上用任意一个都行,不过一般和创建定时器的方法相对应以使代码清晰易读。
定时器如何工作
单看方法的描述,我们很容易误解,天真地以为就是它说的那样。这个时候我们就不要忘记最重要的概念:单线程。事实上,单线程环境中的定时器可能一点也不那么定时!
由于单线程的限制,我们用来处理异步事件(定时器)的回调函数,只有当没有其他代码在执行时才会得到执行。因此,这些事件处理函数(定时器回调)会排队执行,当其中一个执行时,另外一个必须等着。JQuery之父有一篇博客很好地描述了定时器的机制,其中 有一幅图非常经典,我在这里把它贴出来并解释一下。
从上往下表示的是时间的流逝,蓝色的方框表示的是一段段代码。第一段代码(可以看成我们的主要逻辑)中创建了一个10ms的setTimeout和一个10ms的setInterval,在第一段代码里面还未执行完时,10ms的setTimeout就触发了,但此时它的处理函数并不会立即被执行(单线程),而是加到了队列中等待时机。在setTimeout触发之前还有一个鼠标单击事件触发,像鼠标单击事件这样的界面事件也是异步处理的,所以它也不会被立即执行,同样被放到了队列中。
等第一个代码段执行完毕后,现在cpu空闲了,于是问,有谁还在排队等待执行的吗?这时我们知道队列中有两个事件处理函数等着,一般情况下,浏览器会采取先进先出的原则取出最先入队的函数运行(本例中假设如此,要注意这不是确定的,浏览器也可能有自己的特定算法),于是鼠标点击事件响应函数得到执行,在它执行的过程中setInterval的第一次触发,但cpu说,不好意思,我很忙,你先排着吧。
到鼠标点击事件处理(第二段代码)完毕后,队列中还剩早就触发却还在苦逼地等着的setTimeout的回调函数以及新加入的setInterval的回调函数。又一次闲下来的cpu于是取出setTimeout的函数开始执行(泪流满面),还没执行多久,setInterval第二次触发了,但悲剧的是,第一次触发都还没执行!是不是又继续排队呢?浏览器会说,队列中已经有了一个在等待了,你这同一个setInterval就别再掺和了,于是,直接忽略这次触发!等setTimeout处理完后,终于轮到了setInterval的第一次触发执行了,在它快要执行完时(40ms),setInterval第三次触发,这时由于队列中已经没有同一个setInterval等着,因此会正常进入队列等待。
后面到setInterval的第三次触发响应执行完后,队列空了,于是暂时就没啥 事干了,等到第四次触发时由于没有人在排队,于是响应函数马上得以执行,这次总算准了一回!
总之,由于JavaScript是单线程的,我们永远无法确定我们设的定时器会按照我们指定的延迟时间准确地执行。
setTimeout和setInterval的区别
setInterval从功能上很像是一个setTimeout周期性地重复调用自己,但它们并不等同,存在着微妙的差别。
比如下面的两段类似的代码:
setTimeout(function repeatMe() {
/* 一段代码... */
setTimeout(repeatMe, 10);
}, 10);
setInterval(function() {
/* 一段代码... */
}, 10);
你可能会觉得这两种方式从功能上等价,事实上不然。
- setTimeout保证的是下一次回调执行至少在这次回调执行完的10ms以后,也就是说不少于10ms。
- setInterval则会每隔10ms去尝试执行一次回调,不管上一次回调什么时候执行完(但setInterval的每一次尝试并不一定会成功,也就是说同一个setInterval的两次触发不会都被排队)
异步事件
浏览器是事件驱动的。在浏览器看来,绝大部分动作都是异步的,这些动作只会触发一个事件并加到队列中。
除了上面详细描述的定时器外,还包括鼠标移动、窗口大小调整等动作都会触发一个异步事件。
浏览器内部有一个事件循环,它的责任就是检查队列和处理事件。事实上绝大多数交互和活动都会通过事件循环来运行。有时候一个简单的动作可能会导致一系列事件加入队列中,比如单击鼠标还会伴随触发鼠标按下和鼠标释放两个动作。这跟桌面GUI程序的原理是一致的。
setTimeout(func, 0)有啥用
我们经常会看到把setTimeout的第2个参数即延迟设为0,这不是说马上执行吗?那为何还要用setTimeout呢?看了先前对setTimeout工作机制的描述后,应该会明白,这样是告诉浏览器,尽快执行我要的动作,但尽快并不等于立即!还是得放到队列中,最快也要等浏览器的内部循环的下一个周期来到时才能执行。
这种用法是一个很好的技巧,我自己就用过下面的这个经典例子,目的是想使一个input中输入的字母强制转换成大写。乍一看,我们会不太费力地想到如下实现:
document.getElementById('myInput').onkeypress = function(event) {
this.value = this.value.toUpperCase();
}
我开始也是这样写的,但事与愿违,输入框中的最后一个字母没变成大写。原因在于浏览器会在keypress事件处理后才会将输入的字符加到输入框中。这就是setTimeout大显身手的时候了:
document.getElementById('myInput').onkeypress = function(event) {
var that = this;
setTimeout(function() {
that.value = that.value.toUpperCase();
}, 0);
}
这样就会发现问题解决了。是不是很神奇?这样用实际上是为了让浏览器将字符先加到输入框中,然后才执行我们想要的转换大小写的动作,当然这个间隔足够短,As Soon As Possible!
另外它也常用来延迟子元素上的事件触发,让父元素的事件处理先起作用。由于事件响应是自下向上冒泡的,通常会先处理子元素的事件,如果我们要反过来呢?看下面的代码:
var input = document.getElementsByTagName('input')[0];
input.onclick = function() {
setTimeout(function() {
input.value +=' input'
}, 0);
};
document.body.onclick = function() {
input.value += ' body';
};
通过在input这个子元素的click事件处理函数中用这个技术,我们就可以让body父元素的先响应click事件。
定时器、界面渲染、耗时任务
对于大多数浏览器来说,JavaScript的执行和浏览器页面渲染共用同一个线程,这意味着,在JavaScript执行的时候会阻塞界面的渲染。所以如果我们在JavaScript中做长时间的计算密集型的操作,会导致界面失去响应,这对用户来说极不友好。另外,一些浏览器直接会警告用户你的脚本失去响应,甚至强行终止掉你的脚本。所以我们要避免一次执行一个过于耗时的任务,如果有这种任务,我们可以通过定时器来对任务进行拆分,以腾出机会给到界面渲染,使界面处于可交互的状态。
比如类似这样的对DOM进行大量更改的操作:
var tbody = document.getElementsByTagName("tbody")[0];
for (var i = 0; i < 20000; i++) {
var tr = document.createElement("tr");
for (var t = 0; t < 6; t++) {
var td = document.createElement("td");
td.appendChild(document.createTextNode(i + "," + t));
tr.appendChild(td);
}
tbody.appendChild(tr);
}
上面的代码可能会非常耗时,而且界面的渲染不可同时进行,意味着你这段代码执行完毕之前,页面不会有任何动静,所以在这种情况下,就应该利用好定时器,一次做一部分,然后让出执行权,让界面逐步渲染。
后记
JavaScript定时器及其他的更多相关文章
- Javascript定时器(三)——setTimeout(func, 0)
setTimeout(func, 0)可以使用在很多地方,拆分循环.模拟事件捕获.页面渲染等 一.setTimeout中的delay参数为0,并不是指马上执行 <script type=&quo ...
- Javascript定时器(二)——setTimeout与setInterval
一.解释说明 1.概述 setTimeout:在指定的延迟时间之后调用一个函数或者执行一个代码片段 setInterval:周期性地调用一个函数(function)或者执行一段代码. 2.语法 set ...
- JavaScript定时器详解
假设有以下场景 setTimeout(function timeoutHandler(){ /*Some timeout handle code that runs for 6ms*/ }, 10); ...
- Javascript 定时器调用传递参数的方法
文章来源: https://m.jb51.net/article/20880.htm 备注:先记下,以后整理: Javascript 定时器调用传递参数的方法,需要的朋友可以参考下. 无论是wind ...
- JavaScript定时器的工作原理(翻译)
JavaScript定时器的工作原理(翻译) 标签(空格分隔): JavaScript定时器 最近在看ajax原理的时候,看到了一篇国外的文章,讲解了JavaScript定时器的工作原理,帮助我很好的 ...
- JavaScript定时器作业
JavaScript定时器作业 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta char ...
- 关于JavaScript定时器我的一些小理解
因为自己在平时工作中,有些功能需要用到定时器,但是定时器并不像我们表边上看到的那样,所以这周末我看看书查查资料,深入研究了一下JavaScript中的定时器,那么废话不多说,下面进入我们今天的正题. ...
- Javascript 定时器的使用陷阱 (setInterval)
setTimeout(function(){ // 其他代码 setTimeout(arguments.callee, interval); }, interval); setInterval会产生回 ...
- JavaScript定时器越走越快的问题
目录 JavaScript定时器越走越快的问题 (setinterval)多次初始化 清除(clearInterval)的失效 解决方法 JavaScript定时器越走越快的问题 之前在项目中写了定时 ...
- JavaScript定时器及回调用法
JavaScript定时器及回调用法 循环定时任务 // 假设现在有这样一个需求:我需要请求一个接口,根据返回结果判断需不需要重复请求,直到达到某一条件为止,停止请求执行某操作 <script ...
随机推荐
- IT领域的罗马帝国——微软公司
微软公司从做软件开始,起步很小.但是盖茨确是一直深耕于战略布局,像一个棋局高手,每一步棋都是看了后面几步. 盖茨居然用9年的时间憋出一个win3.0,成功击败了apple. 而这9年拖住apple的居 ...
- 晶振虚焊导致TI 28335 DSP 烧写FLASH后,连接仿真器时正常工作,拔掉仿真器却不能启动运行
遇到个诡异的问题,28335的DSP,之前程序调试一切正常,但是烧写FLASH后,拔掉仿真器却始终部工作. 解决思路: 1) 检查配置文件貌似没什么问题,复制到其他工程,在开发板上拔掉仿真器启动正常. ...
- 非常适合新手的jq/zepto源码分析02
function isPlainObject(obj) { return isObject(obj) && !isWindow(obj) && Object.getPr ...
- ojdbc.jar
Oracle的jdbc驱动是ojdbc.jar 文件,那么mysql的jdbc驱动是什么呢? 匿名 | 浏览 689 次 发布于2015-06-07 02:06 最佳答案 MySQL的JDBC ...
- OC中APPDelegate[[UIApplication shareApplication]delegate]]Swift实现
直接上代码: var myDelegate:AppDelegate? myDelegate = UIApplication.sharedApplication().delegate as? AppDe ...
- Unity5.1 新的网络引擎UNET(十五) Networking 引用--中
孙广东 2015.7.21 本节提供了与网络系统一起使用的组件的具体信息. 3.NetworkClient NetworkClient 是一个 HLAPI 类,管理网络连接到服务器 - - 相应着 U ...
- leetCode(49):Count Primes
Description: Count the number of prime numbers less than a non-negative number, n. 推断一个数是否是质数主要有下面几种 ...
- Oracle行转列,列转行,行列相互转换
1.行转列 SELECT WM_CONCAT(COLUMN_NAME) COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'T_CREATE_T ...
- XAML实例教程系列 - XAML传递参数到值转换类实例 八
Kevin Fan分享开发经验,记录开发点滴 XAML实例教程系列 - XAML传递参数到值转换类实例 2012-06-28 05:25 by jv9, 508 阅读, 0 评论, 收藏, 编辑 继上 ...
- JSP-Runoob:JSP 异常处理
ylbtech-JSP-Runoob:JSP 异常处理 1.返回顶部 1. JSP 异常处理 当编写JSP程序的时候,程序员可能会遗漏一些BUG,这些BUG可能会出现在程序的任何地方.JSP代码中通常 ...