JavaScript -- 定时器

介绍

JavaScript 提供了两个方法供我们设置一个定时器,它们分别是 setTimeout()setInterval()。这两种方法的使用方法是相同的,都接收两个参数,第一个参数是一个回调函数,第二个参数是延迟的毫秒数。这就造成了一种 JavaScript 是多线程语言的假象,因为相同的功能在 Java 中称之为 sleep() 或者 Lock.lock(),它们的作用都是堵塞当前线程,为其他线程腾出处理器资源。

实际上 JavaScript 是运行在单线程环境中的,它拥有一个事件处理队列,所有要处理的事件都会被放置在这个队列中排队等待执行。这样的话就出现了一个问题,浏览器并不能保证我们的代码会在指定的时间内执行。

举个例子来说,如果一个事件的执行时间非常长,那么在这个时间的执行过程中,我们点击页面上的任何按钮或者其他可点击控件,都无法得到回馈,因为我们的点击事件正在队列中排队执行。

从上面的介绍中,我们明白了一个道理,那就是不能让一个事件处理时间过长,否则就会导致用户无法与页面进行体验良好的交互。所以,现在有很多技巧用于处理耗时操作,比如函数节流和分块处理,接下来我会讲解这些技巧。

两种方法的比较

setTimeout() 的作用是在指定时间内执行一个任务。setInterval() 的作用是以指定时间周期性的执行任务。

按理说,这两种方法分工明确,我们应该根据自身的需要选择使用 setTimeout() 或者 setInterval(),但是目前的最佳实践却是始终使用 setTimeout(),即在应该使用 setTimeout() 的时候使用 setTimeout(), 在应该使用 setInterval() 的时候用 setTimeout() 去替代。

原因就是使用 setInterval() 的时候会出现间隔跳过问题。比如我们设置了一个 setInterval(callback, 10),如果这个 callback 的执行时间是 20ms,那么就会出现无间隔连续执行 callback 的情况,不过 JavaScript 引擎处理的过程却不和我们想象的一样,如果当前事件队列中已经有定时器代码实例了,它就不会再放一个相同的定时器进去。这也就导致一部分定时器会被跳过的问题。

下面是利用 setTimeout() 代替 setInterval() 的例子。

        function callback() {
            console.log("Hello World!");
        }
        setInterval(callback, 10);

        // After
        function callback() {
            console.log("Hello World");
            setTimeout(callback, 10);
        }
        setTimeout(callback, 10);

使用了 setTimeout() 之后,可以保证在一个定时器任务执行之后才会再次将一个定时器插入队列,不会有丢失间隔的问题。


高级技巧

  • 分块处理

导致脚本长时间运行的两个主要原因就是过深过长的嵌套函数调用和包含大量处理过程的循环。对于后一个问题,我们可以对循环进行切割,分时处理,腾出时间为其他事件进行服务。

        function chunk(array, process, context) {
            setTimeout(function() {
                var item = array.shift();
                process.call(context, item);

                if(array.length > 0) {
                    setTimeout(arguments.callee, 100);
                }
            }, 100);
        }

        var data = [1, 2, 3, 4, 5, 6];
        function printValue(i) {
            console.log(i);
        }

        chunk(data, printValue);

可见,在以上代码中我们以 100ms 的间隔去执行打印事件,这样的话在间隔的过程中,浏览器就能很好的处理与用户的交互。在执行耗时任务时,这一技巧十分重要,因为网页的明显卡顿会让你的用户离你而去。

  • 函数节流
    因为 JavaScript 是在浏览器中执行的,所以它的限制非常大,这也就迫使我们去考虑代码的性能以及资源的利用问题。函数节流的思想就是,某些代码不可以在没有间隔的情况下连续执行。举个例子来说,如果用户疯狂的点击页面中的一个按钮,频率非常之高,这个时候就不能按照用户点击的次数去调用处理程序。难道用户一秒点击 20 次按钮我们还要重复的执行 20 次处理程序吗?这是完全没有必要的,所以我们可以采取 setTimeout() 让用户请求结束后一段时间再去执行。

          var clickButton = document.getElementById("click");
    
          function throttle(method, context) {
              clearTimeout(method.tId);
    
              method.tId = setTimeout(function() {
                  method.call(context);
              },2000);
          }
    
          function print() {
              console.log("You click the button!");
          }
    
          clickButton.onclick = function() {
              throttle(print);
          }    
    

以上代码就保证了在 2s 之内无论你点击了多少次按钮,事件处理函数只会执行一次,当然设置 2s 只是试验性的,在实际开发过程中,2s 可能太长了,需要改成一个较小的值,比如说 100ms 。

  • 中央定时器控制
    如果我们同时创建了大量的定时器,将会在浏览器中增加垃圾回收任务发生的可能性。所以为了避免我们的定时器被当做垃圾回收掉,可以使用中央定时器控制的技术。下面是中央定时器控制的一些特点:
  1. 每个页面在同一时间只需要运行一个定时器。

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

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

      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;
        }
     };
    

上述代码就创建了一个简单的中央定时器,首先我们在 timers 中定义了 timerId 和 timers, timerId用于保存定时器的 id,用于控制定时器的开启和关闭,timers 用于保存要执行的函数。

最主要的还是 start 函数,首先对 timerId 进行判断,如果还没有启动定时器,则立即启动一个,如果已经有定时器启动了,则直接返回,用于确保整个环境中只存在一个定时器实例。每次执行定时器实例,都会执行一次保存在 timers 队列中的函数,如果执行的函数返回结果为 false, 就会将其从队列中删除,这样下次就不会再执行该函数了。


End!

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

推荐阅读更多精彩内容

  • 一、什么是定时器 JS提供了一些原生方法来实现延时去执行某一段代码,下面来简单介绍一下 setTimeout: 设...
    SSSSSSH阅读 924评论 1 50
  • 1、下面这段代码输出结果是? 为什么? JavaScript 所有任务可以分成两种,一种是同步任务(synchro...
    zh_yang阅读 181评论 0 0
  • 9.26-9.30 第8章 驯服线程和定时器 定时器可以在js中使用,但它不是js的一项功能,如果我们在非浏览器环...
    如201608阅读 573评论 0 2
  • 每天一句:如果,感到此时的自己很辛苦,那告诉自己:容易走的都是下坡路!坚持住,因为你正在走上坡路,走过去,你就一定...
    EndEvent阅读 308评论 0 0
  • 先上一组图,感受一下: 如此眼花缭乱的盘盘碗碗和盛在其中的美食,让我口水飞流三千尺。 这是英国的迈克尔·徐为男友马...
    诗涵Ady阅读 326评论 0 0