30天学习计划 js忍者秘籍 第8章 驯服线程和定时器

9.26-9.30

第8章 驯服线程和定时器

定时器可以在js中使用,但它不是js的一项功能,如果我们在非浏览器环境中使用js,很可能定时器就不存在了,需要自己实现自己的定时器版本。

定时器提供了一种让一段代码在一定毫秒之后,再异步执行的能力。由于js是单线程的特性(同一时间只能执行一处js代码),定时器提出了一种跳出这种限制的方法,以一种不太直观的方式来执行代码。

8.1 定时器和线程是如何工作的

8.1.1 设置和清除定时器

js提供了两种方式,用于创建定时器以及两个相应的清除方法(删除)。这些方法是window对象(全局上下文)上的方法。

id = setTimeout(fn,delay) 启动一个定时器,在一段时间(delay)之后执行传入的callback,并返回该定时器的唯一标识

clearTimeout(id) 如果定时器还未触发,传入定时器标识即可取消(清除)该定时器

id = setInterval(fn,delay) 启动一个定时器,在每隔一段时间之后都执行传入的callback,并返回该定时器的唯一标识

clearInterval(id) 传入间隔定时器标识,即可取消该间隔定时器

js定时器的延迟时间是不能保证的,原因和js线程的本质有很大关系。

8.1.2 执行线程中的定时器执行

在Web worker可用之前,浏览器中的所有js代码都是在单线程中执行的,是的,只有一个线程。

处理程序在执行时必须进行排队执行,并且一个处理程序并不能中断另外一个处理程序的执行。

8.1.3 timeout与interval之间的区别

示例8.1 两种创建重复定时器的方式

setTimeout(function repeatMe(){  //定义一个timeout定时器,每10毫秒都重新调用自己

//code

setTimeout(repeatMe,10)

},10)

setInterval(function(){  //定义一个interval定时器,每10毫秒都触发一次

//code

},10)

在setTimeout()代码中,要在前一个callback回调执行结束并延迟10秒以后,才能再次执行setTimeout()。

而setInterval()则是每隔10毫秒就尝试执行callback回调,而不关注上一个callback是何时执行的。

.js引擎是单线程执行,异步事件必须要排队等待才能执行

.如果无法立即执行定时器,该定时器会被推迟到下一个可用的执行时间点上(可能更长,但不会比指定的延迟时间更少)。

.如果一直被延迟,到最后,interval间隔定时器可能会无延迟执行,并且同一个interval处理程序的多个实例不能同时进行排队。

.setTimeout()和setInterval()在触发同期的定义上是完全不同的。

8.2 定时器延迟的最小化及其可靠性

现代浏览器通常无法实现1毫秒粒度的可持续间隔,某些浏览器的实现可以非常接近。

当我们对setInterval()设置0毫秒的延迟时,ie浏览器定时器的callback回调只会执行一次,和使用setTimeout效果一样。

浏览器不保证我们指定的延迟间隔,虽然可以指定特定的延迟值,但其准确性却并不总是能够保证,尤其是在延迟值很小的时候。

8.3 处理昂贵的计算过程

js的单线程本质可能是js复杂应用程序开发中的最大“陷阱”。在js执行的时候,页面渲染的所有更新操作都要暂停。

如果要保持界面有良好的响应能力,减少运行时间超过几百毫秒的复杂操作,将其控制在可管理状态是非常必要的。

如果一段脚本的运行时间超过5秒,有些浏览器将弹出一个对话框警告用户该脚本“无法响应”。iPhone上的浏览器,将默认终止运行时间超过5秒钟的脚本。

作为定时器,它在一段时间之后,可以有效暂停一段js代码的执行,定时器还可以将代码的各个部分,分解成不会让浏览器挂掉的碎片。

考虑到这一点,我们可以将强循环和操作转化为非阻塞操作。

示例8.2 一个长时间运行的任务

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)

}

上例创建了240000个DOM节点,并使用大量的单元格来填充一个表格。这是非常昂贵的操作,明显会增加浏览器的执行时间,从而阻止正常的用户交互操作。

我们可以引入定时器,在代码执行的时候定期暂停休息

示例8.3 利用定时器分解长时间运行的任务

var tbody = document.getElementsByTagName('tbody')[0];

var rowCount = 20000;

var divideInto = 4;

var chunkSize = rowCount/divideInto;

var iteration = 0;

setTimeout(function generateRows(){

var base = (chunkSize)*iteration;

for(var i=0; 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)

}

tbody.appendChild(tr);

}

iteration++;

if(iteration

setTimeout(generateRows,0)

}

},0);

上例将操作分成四步小操作,每个操作创建自己的DOM节点。这些较小的操作,则不太可能让浏览器挂掉。

8.4 中央定时器控制

使用定时器可能出现的问题是对大批量定时器的管理。

同时创建大量的定时器,将会在浏览器中增加垃圾回收任务的可能性。垃圾回收就是浏览器遍历其分配过的内存,并试图删除没有任何应用的未使用对象的过程。定时器是一个特殊的问题,因为通常它们是在js单线程引擎之外的流程中进行管理。有些浏览器可以很好地处理这种情况,有些浏览器的垃圾回收周期则很长。一个动画在某个浏览器中很漂亮、很流畅,但在另外一个浏览器中却很卡顿。

在多个定时器中使用中央定时器控制,可以带来很大的威力和灵活性。

.每个页面在同一时间只需要运行一个定时器。

.可以根据需要暂停和恢复定时器。

.删除回调函数的过程变得很简单。

示例8.4 管理多个处理程序的中央定时器控制

test suite

#box{position:absolute;width:60px;height:40px;border:1px solid #060; text-align:center;}

Hello!

var timers={

timerID:0,

timers:[],

add:function(fn){

this.timers.push(fn);

},

start:function runNext(){

if(this.timerID) return;

(function(){

if(timers.timers.length > 0){

for(var i=0; i

if(timers.timers[i]() === false){

timers.timers.splice(i,1);

i--

}

}

timers.timerID = setTimeout(runNext,0)

}

})()

},

stop:function(){

clearTimeout(this.timerID);

this.timerID=0;

}

}

var box = document.getElementById('box'),x=0,y=20;

timers.add(function(){

box.style.left = x + 'px';

if(++x>50) return false;

})

timers.add(function(){

box.style.top = y+'px';

y+=2;

if(y>120) return false;

})

timers.start();

一开始,所有的回调函数都存储于一个名为timers的数组中,还包含当前定时器的一个ID,这些变量是定时器唯一需要维护的内容。

add()方法接受一个callback回调,并简单将其添加到timers数组中。

start()方法首先确认没有定时器在运行(通过检查timerID是否有值),如果确认没有定时器在执行,立即执行一个即时函数来开启中央定时器。

在即时函数内,如果注册了处理程序,就遍历执行每个处理程序。如果有处理程序返回false,我们就从数组中将其删除,最后进行下一次调度。

以这种方式组织定时器,可以确保回调函数总是按照添加的顺序进行执行。而普通的定时器通常不会保证这种顺序,有可能后面的一个处理程序在前面就执行了。

这种方式的定时器组织,对于大型应用程序或任何形式的js动画来说都是至关重要的。

8.5 异步测试

示例:简单的异步测试套件

(function(){

var queue = [],paused=false;

this.test = function(fn){

queue.push(fn);

runTest();

}

this.pause = function(){

paused = true;

}

this.resume = function(){

paused = false;

setTimeout(runTest,1)

}

function runTest(){

if(!paused && queue.length){

queue.shift();

if(!paused) resume();

}

}

})()

示例中,传递给test()方法的每个函数,最多只包含一个异步测试。它们的异步性由pause()和resume()的使用所定义,这两个方法分别在异步事件之前或之后进行调用。这段代码是一种确保让包含异步行为的函数,以特定的顺序进行执行的方式。

该队列唯一的目的是在等待执行的时候,出列一个函数并进行执行。否则,就完全停止运行一个时间间隔。

这段代码,强制测试套件以纯粹异步方式进行执行,但同时又保证了测试执行的顺序。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容