专栏名称: 程序员之家
程序员第一自媒体,与你探讨码农人生路上遇到的各类泛技术话题,定期为你推荐码农人生思考、感悟以及启迪!
目录
相关文章推荐
程序员的那些事  ·  趣图:机器人也挺不容易的 ·  5 天前  
程序猿  ·  甲骨文正式发布VirtualBox ... ·  4 天前  
赛尔实验室  ·  CCIR 2024 会议大会主旨报告预告 ... ·  2 天前  
OSC开源社区  ·  “我们为什么一定要投资中国” ·  1 周前  
51好读  ›  专栏  ›  程序员之家

setTimeout 的黑魔法

程序员之家  · 公众号  · 程序员  · 2017-04-28 21:59

正文

作者:李三思

原文:www.cnblogs.com/fly-snow/archive/2016/04/24/5427865.html

点击文末阅读原文即可前往) 


setTimeout,前端工程师必定会打交道的一个函数.它看上去非常的简单,朴实.有着一个很不平凡的名字–定时器.让年少的我天真的以为自己可以操纵未来.却不知朴实之中隐含着惊天大密.我还记得我第一次用这个函数的时候,我天真的以为它就是js实现多线程的工具.当时用它实现了一个坦克大战的小游戏,玩儿不亦乐乎.可是随着在前端这条路上越走越远,对它理解开始产生了变化.它似乎开始蒙上了面纱,时常有一些奇怪的表现让我捉摸不透.终于,我的耐心耗尽,下定决心,要撕开它的面具,一探究竟.


要说setTimeout的渊源,就得从它的官方定义说起.w3c是这么定义的


setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。


看到这样一个说明,我们明白了它就是一个定时器,我们设定的函数就是一个”闹钟”,时间到了它就会去执行.然而聪明的你不禁有这样一个疑问,如果是settimeout(fn,0)呢?按照定义的说明,它是否会立马执行?实践是检验真理的唯一标准,让我们来看看下面的实验


DOCTYPE html>

html lang="en">

head>

    meta charset="UTF-8">

    title>title>

head>

body>

    

    

    meta charset="UTF-8">

    title>title>

head>

body>

    

body>

head>

    meta charset="UTF-8">

    title>title>

head>

body>

    table border=1>

        tr>td>button id='do'>Do long calc - bad status!button>td>

            td>div id='status'>Not Calculating yet.div>td>

        tr>

        tr>td>button id='do_ok'>Do long calc - good status!button>td>

            td>div id='status_ok'>Not Calculating yet.div>td>

        tr>

    table>    

            for (var k = 0; k 300; k++) {

                result = result + i + j + k;

            }

        }

    }

    document.querySelector(status_div).innerHTML = 'calclation done' ;

}


document.querySelector('#do').onclick = function () {

    document.querySelector('#status').innerHTML = 'calculating....';

    long_running('#status');

};


document.querySelector('#do_ok').onclick = function () {

    document.querySelector('#status_ok').innerHTML = 'calculating....';

    window.setTimeout(function (){ long_running('#status_ok') }, 0);

};


body>

html>


我们希望能看到计算的每一个过程,我们在程序开始,计算,结束时,都执行了一个dom操作,插入了代表当前状态的字符串,Not Calculating yet.和calculating….和calclation done.计算中是一个耗时的3重for循环. 在没有使用settimeout的时候,执行结果是由Not Calculating yet 直接跳到了calclation done.这显然不是我们希望的.而造成这样结果的原因正是js的事件循环单线程机制.dom操作是异步的,for循环计算是同步的.异步操作都会被延迟到同步计算之后执行.也就是代码的执行顺序变了.calculating….和calclation done的dom操作都被放到事件队列后面而且紧跟在一起,造成了丢帧.无法实时的反应.这个例子也告诉了我们,在需要实时反馈的操作,如渲染等,和其他相关同步的代码,要么一起同步,要么一起异步才能保证代码的执行顺序.在js中,就只能让同步代码也异步.即给for计算加上settimeout.


settimeout(0)的作用


不同浏览器的实现情况不同,HTML5定义的最小时间间隔是4毫秒. 使用settimeout(0)会使用浏览器支持的最小时间间隔.所以当我们需要把一些操作放到下一帧处理的时候,我们通常使用settimeout(0)来hack.


requestAnimationFrame


这个函数与settimeout很相似,但它是专门为动画而生的.settimeout经常被用来做动画.我们知道动画达到60帧,用户就无法感知画面间隔.每一帧大约16毫秒.而requestAnimationFrame的帧率刚好是这个频率.除此之外相比于settimeout,还有以下的一些优点:


  • requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧,每帧大约16毫秒.

  • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

  • 但它优于setTimeout/setInterval的地方在于它是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。


总结:


  1. 浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。

  2. javascript引擎是基于事件驱动单线程执行的.JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。

  3. 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

  4. 当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。



微信公众号内回复数字“1”

小编拉你进粉丝微信群

不是在文章评论里回复