js定时器的理解

概念

  • 人们对 JavaScript的定时器存在广泛的误解,认为它们是线程,其实 JavaScript 是运行于单线程的环境中的,而定时器仅仅只是计划代码在将来的某个时间执行。执行时机是不能保证的,由于在页面的生命周期中,不一样时间可能有其余代码在控制JavaScript进程。在页面下载完后的代码运行、事件处理程序、Ajax回调函数都必须使用一样的线程来执行。实际上,浏览器负责进行排序,指派某段代码在某个时间点运行 的优先级。java

  • 定时器对队列的工做方式是,当特定时间过去后将代码插入。注意,给队列添加代码并不意味着对它马上执行,而只能表示它会尽快执行。设定一个 150ms 后执行的定时器不表明到了 150ms 代码就马上 执行,它表示代码会在150ms后被加入到队列中。若是在这个时间点上,队列中没有其余东西,那么这段代码就会被执行,表面上看上去好像代码就在精确指定的时间点上执行了。其余状况下,代码可能明显地等待更长时间才执行。web

setTimeout

  • 执行完一套代码后,JavaScript进程返回一段很短的时间,这样页面上的其余处理就能够进行了。因为JavaScript进程会阻塞其余页面处理,因此必须有这些小间隔来防止用户界面被锁定(代码长时间 运行中还有可能出现)。这样设置一个定时器,能够确保在定时器代码执行前至少有一个进程间隔。

setInterval

  • 使用 setInterval()建立的定时器确保了定时器代码规则地插入队列中。这个方式的问题在于,定时器代码可能在代码再次被添加到队列以前尚未完成执行,结果致使定时器代码连续运行好几回, 而之间没有任何停顿。幸亏,JavaScript引擎够聪明,能避免这个问题。当使用 setInterval()时,仅当没有该定时器的任何其余代码实例时,才将定时器代码添加到队列中。这确保了定时器代码加入到队列中的最小时间间隔为指定间隔。
  • 这种重复定时器的规则有两个问题:

(1) 某些间隔会被跳过;浏览器

(2) 多个定时器的代码执行之间的间隔可能会比预期的小。bash

为了不 setInterval()的重复定时器的这 2 个缺点,你能够用以下模式使用链式 setTimeout() 调用。函数

setTimeout(function(){ //处理中
    setTimeout(arguments.callee, interval);//次优选项 可写个function代替
}, interval);

复制代码

这个模式链式调用了setTimeout(),每次函数执行的时候都会建立一个新的定时器。第二个 setTimeout()调用使用了 arguments.callee来获取对当前执行的函数的引用,并为其设置另一 个定时器。这样作的好处是,在前一个定时器代码执行完以前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。并且,它能够保证在下一次定时器代码执行以前,至少要等待指定的间隔,避 免了连续的运行。这个模式主要用于重复定时器,以下例所示。优化

setTimeout(function(){
        var div = document.getElementById("myDiv");
        left = parseInt(div.style.left) + 5;
        div.style.left = left + "px";
        if (left < 200){
           setTimeout(arguments.callee, 50);
        }
    }, 50);
复制代码

requestAnimationFrame

  • 大多数电脑显示器的刷新频率是 60Hz,大概至关于每秒钟重绘 60 次。大多数浏览器都会对 重绘操做加以限制,不超过显示器的重绘频率,由于即便超过那个频率用户体验也不会有提高。
  • 所以,最平滑动画的最佳循环间隔是 1000ms/60,约等于 17ms。以这个循环间隔重绘的动画是最平 滑的,由于这个速度最接近浏览器的最高限速。为了适应 17ms 的循环间隔,多重动画可能须要加以节 制,以便不会完成得太快。
  • 虽然与使用多组setTimeout()的循环方式相比,使用setInterval()的动画循环效率更高,但后者也不是没有问题。不管是setInterval()仍是setTimeout()都不十分精确。为它们传入的第二个参数,实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。若是队列前面已经加入了其余任务,那动画代码就要等前面的任务完成后再执行。简言之,以毫秒表示的延迟时间并不表明到时候必定会执行动画代码,而仅表明到时候会把代码添加到任务队列中。若是UI线程繁忙,好比忙于处理用户操做,那么即便把代码加入队列也不会当即执行。
  • 知道何时绘制下一帧是保证动画平滑的关键。然而,直至最近,开发人员都没有办法确保浏览 器按时绘制下一帧。
  • 一个很是独特的方案。 CSS变换和动画的优点在于浏览器知道动画何时开始,所以会计算出正确的循环间隔,在恰当的时候刷新UI。而对于JavaScript动画,浏览器无从知晓何时开始。所以他的方案就是创造一个新方法mozRequestAnimationFrame(),经过它告诉浏览器某些JavaScript代码将要执行动画。这样浏览器能够在运行某些代码后进行适当的优化。
(function(){
        function draw(timestamp){
             //timestamp 它是一个时间码(从1970 年1月1日起至今的毫秒数),表示下一次重绘的实际发生时间。
             //计算两次重绘的时间间隔
            var drawStart = (timestamp || Date.now()),
            diff = drawStart - startTime; //使用 diff 肯定下一步的绘制时间
            //把 startTime 重写为这一次的绘制时间 startTime = drawStart;
            //重绘 UI
            requestAnimationFrame(draw);
        }
        var requestAnimationFrame = window.requestAnimationFrame ||
                                    window.mozRequestAnimationFrame ||
                                    window.webkitRequestAnimationFrame ||
                                    window.msRequestAnimationFrame,
            startTime = window.mozAnimationStartTime || Date.now();
 })();
复制代码

疑问?

1.setInterval 与 setTimeout 模拟的setInterval有什么区别?动画

答:ui

  • setInterval以下:
setInterval(() => {
    //定时器代码
}, 200);
复制代码

每200ms向进程插入一次定时器代码,在第5ms,205ms,405ms处都插入了定时器代码,因为这样的一条规则:spa

当使用 setInterval()时,仅 当没有该定时器的任何其余代码实例时,才将定时器代码添加到队列中。线程

因此第5ms处,205ms都成功将定时器代码插入js进程中,可是第405ms处因为正在执行定时器代码故插入失败。使这个 间隔被跳过 ,同时执行完第一次定时器代码后紧接着执行了第二次定时器代码,使 多个定时器的代码执行之间的间隔可能会比预期的小

  • 使用setTimeout
setTimeout(function(){ 
    //定时器代码
    setTimeout(arguments.callee, interval);
}, interval);
复制代码

这个模式链式调用了setTimeout(),每次函数执行的时候都会建立一个新的定时器。第二个setTimeout()调用使用了arguments.callee 来获取对当前执行的函数的引用,并为其设置另一个定时器。这样作的好处是,在前一个定时器代码执行完以前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。并且,它能够保证在下一次定时器代码执行以前,至少要等待指定的间隔,避 免了连续的运行。

2.以下代码的 clearTimeout(s)是否能够清楚定时器。

var i = 0
    var s = setTimeout(function(){
                add()
            },300)
    function add(){
        i++
        console.log(123);
        console.log(i);
        if(i>20){
            clearTimeout(s)
        }else{
            setTimeout(function(){
                add()
            },300)
        }
    }
复制代码

答:可

3.arguments.callee 是什么意思?

  • arguments.callee 属性包含当前正在执行的函数。
  • callee 是 arguments对象的一个属性。它能够用于引用该函数的函数体内当前正在执行的函数。这在函数的名称是未知时颇有用,例如在没有名称的函数表达式 (也称为“匿名函数”)内。

警告:在严格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()。当一个函数必须调用自身的时候, 避免使用 arguments.callee(), 经过要么给函数表达式一个名字,要么使用一个函数声明.

相关文章
相关标签/搜索