JavaScript忍者秘籍——驯服线程和定时器

1.定时器和线程

- 设置和清除定时器

  JavaScript提供了两种方式,用于创建定时器以及两个相应的清除方法。这些方法都是window对象上的方法。

方法 格式 描述
setTimeout   id=setTimeout(fn,delay)  启动一个定时器,在一段时间(delay)之后执行传入的callback,并返回该定时器的唯一标识
clearTimeout  clearTimout(id) 如果定时器还未触发,传入定时器标识即可取消该定时器
setInterval id=setInterval(fn,delay) 启动一个定时器,在每隔一段时间(delay)之后都执行传入的callback,并返回该定时器的唯一标识
clearInterval clearInterval(id) 传入间隔定时器标识,即可取消该间隔定时器

- timeout 与 interval 之间的区别

setTimeout(function repeatMe(){
    /* Some long block of code... */
    setTimeout(repeatMe,10);
},10);
setInterval(function(){
    /* Some long block of code... */
},10)

  在setTimeout()代码中,要在前一个callback回调执行结束并延迟10秒以后,才能再次执行setTimeout()。而setInterval()则是每隔10毫秒就尝试执行callback回调,而不关心上一个callback是何时执行的。

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

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

3.处理昂贵的计算过程

  JavaScript的单线程本质可能是JavaScript复杂应用程序开发中的最大"陷阱"。如果一个脚本的运行时间超过5秒,有些浏览器将弹出一个对话框警告用户该脚本"无法响应"。而其他浏览器,比如iPhone上的浏览器,将默认终止运行时间超过5秒钟的脚本。

  例如,一个长时间运行的任务:

<table><tbody></tbody></table>
<script>
    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);
    }
</script>

  在本例中,我们创建了240000个DOM节点,并使用大量的单元格来填充一个表格。这是非常昂贵的操作,明显会增加浏览器的执行时间,从而阻止正常的用户交互操作。这种情况下我们可以引入定时器,在代码执行的时候定期暂时休息,示例如下:

// 建立数据
var rowCount = 20000;
var divdeInto = 4;
var chunkSize = rowCount/divideInto;
var iteration = 0;

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

// 计算上次中断的地方
setTimeout(function generateRows(){
    var base = (chunkSize) * iteration;
    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);

  在本例中,我们将冗长的操作拆分成四个小步骤,每个步骤创建自己的DOM节点。这些小步骤,不太可能让浏览器挂掉。

4.中央定时器控制

  同时创建大量的定时器,将会在浏览器中增加垃圾回收任务发生的可能性。大致说来,垃圾回收就是浏览器遍历其分配过的内存,并试图删除没有任何应用的未使用对象的过程。有些浏览器可以很好地处理这种情况,有些浏览器的垃圾回收周期则很长。比如,一个动画在某个浏览器中很流畅,但在另一个浏览器中却很卡顿,减少同时使用定时器的数量,就能解决这种问题,这也是为什么现代动画引擎都使用一种称为中央定时器控制的技术。

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

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

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

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

示例如下:

// 声明一个定时器控制对象
var timers = {
  // 记录状态
    timerID: 0,
    timers: [],
  // 创建添加处理程序的函数
    add: function(fn){
        this.timers.push(fn);
    },
  // 创建开启定时器的函数
    start: function(){
        if (this.timerID) return;
        (function runNext(){
            if(timers.timers.length > 0){
                for(var i = 0; i < timers.timers.length; 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;
    }
};

  在上述代码中,首先创建了一个中央控制结构,我们可以在该结构上添加任意数量的定时器回调函数,而且还可以通过它,启动和停止该结构的执行。此外,在任何时候如果callback回调函数返回了false值,都允许将其删除。

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

  add()方法接受一个callback回调,并简单将其添加到timers数组中。真正核心的方法是start()方法。在该方法内,首先确认没有定时器在运行,如果确认没有定时器在执行,立即执行一个即时函数来开启中央定时器。

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

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()的使用所定义。这两个方法分别在异步事件之前或之后进行调用。

原文地址:https://www.cnblogs.com/koto/p/5826248.html