关于setTimeout & setInterval

前言

之所以写这篇文章是因为上周工作中使用setInterval轮询请求接口时遇到了一些问题,如果哪里理解的不对请大家多多指教~

进入正题

setTimeout和setInteval是window对象上两个主要的定时方法,他们的语法基本相同,但完成功能的却是不同的。
  • settimeout方法是定时程序,也就是在到达某个指定时间后,执行什么事。(执行一次就拉倒)
  • setinterval方法则是表示间隔一定时间反复执行某些事。
定时器的返回值
  • 当我们设置定时器时(不管是setTimeout还是setInterval),都会有一个返回值。这个返回值是一个数字,代表当前是在浏览器中设置的第几个定时器(返回的是定时器序号)。
      let timer1 = setTimeout(() => {
    
      }, 1000)
      console.log(timer1) // 1
    
      let timer2 = setInterval(() => {
    
      },1000)
      console.log(timer2) // 2
    
    • 根据上面两端代码可以知道
      • 1.setTimeout和setInterval虽然是处理不同功能的定时器,但都是浏览器的定时器,所以返回的序号是依次排列的。
      • 2.setInterval设置完成定时器会有一个返回值,不管执行多少次,这个代表序号的返回值不变(设置定时器就有返回值,执行多少次是定时器的处理)。
定时器的清除
  • clearTimeout([定时器的排队序号])

  • clearInterval([定时器的排队序号])

      let timer = setTimeout(() => {
        // 定时器即使清除了,其返回值也不会清除,之后设置定时器的返回值也会在其返回值的基础上继续向后排,
        // 类似于银行的排队领号,即使1号的业务办理完了,后面的人仍是从2号开始继续领号,而不是从1开始。
        clearTimeout(timer)
      }, 1000)
    

注意: 定时器需要手动清除,并且clearTimeout和clearInterval都可以清除setTimeout或setInterval,但并不建议这样做,容易造成混淆。

定时器的this指向
  • 作为第一个参数的函数将会在全局作用域中执行,因此函数内的this将会指向这个全局对象
      let obj = {
        fn() {
          console.log(this) // obj
    
          // 示例1
          let timer1 = setTimeout(function() {
          
            console.log('我是timer1的this指向:', this)  // Window
          }, 1000)
    
          // 示例2 (让定时器函数中的this是obj:使用变量保存的方式)
          let _this = this
          let timer2 = setTimeout(function() {
            console.log('我是timer2的this指向:', _this) // obj
          }, 1000)
    
          // 示例3 (让定时器函数中的this是obj:使用bind方法改变this指针)
          let timer3 = setTimeout(
            function() {
              console.log('我是timer3的this指向:', this) // obj
            }.bind(this),
            1000
          )
    
          // 示例4 (让定时器函数中的this是obj:使用箭头函数,箭头函数中的this继承宿主环境(上级作用域中的this))
          let timer4 = setTimeout(() => {
            console.log('我是timer4的this指向:', this) // obj
          }, 1000)
        }
      }
      obj.fn()
    

setTimeout和setInterval是如何工作的?

涉及到的知识点:JS事件循环机制EVENTLOOP

  • 首先,Javascript是一门单线程的非阻塞的脚本语言:用来与浏览器交互。
    • 单线程:同一时间只能执行一个任务,其他任务就得排队,后续任务必须等到前一个任务结束才能开始执行。
    • 非阻塞:同步任务直接在主线程队列中顺序执行,而异步任务会进入另一个任务队列,不会阻塞主线程。等到主线程队列空了(执行完了)的时候,就会去异步队列查询是否有可执行的异步任务了(异步任务通常进入异步队列之后还要等一些条件才能执行,如ajax请求、文件读写),如果某个异步任务可以执行了便加入主线程队列,以此循环。

注意:异步任务之间并不相同,他们的执行优先级有区别。不同的异步任务会被分为两类:微任务(micro task)和宏任务(macro task)

  • 微任务:new promise(),new MutaionObserver()
  • 宏任务:setInterval(),setTimeout()
    主线程空闲的时候会先去查看微任务队列是否有事件存在,如果存在就会对微任务队列的事件依次调用,直到为空。然后再对宏任务队列依次执行,进入循环。
^ ^.png

使用定时器的时候,千万不要太相信预期,延迟的时间严格来说总是大于xxx毫秒的,至于大多少就要看当时执行的情况了。即使设置为0也不会马上执行,HTM5规范定最小延迟时间不能小于4ms,不同浏览器的实现不一样,比如,Chrome可以设置1ms,IE11/Edge是4ms。

setTimeout

setTimeout注册的函数fn会交给浏览器的定时器模块来管理,延迟时间到了就将fn加入主进程执行队列,如果队列前面还有没有执行完的代码,则又需要花一点时间等待才能执行到fn,所以实际的延迟时间会比设置的长。如在fn之前正好有一个超级大循环,那延迟时间就不是一丁点了。

(function testSetTimeout() {
    console.time('timer');
    const timer = setTimeout(() => {
        console.timeEnd('timer');
    }, 10);
    for(let i = 0; i < 100000000; i++) {}
})();

// timer: 59.364990234375ms 远远不止10ms
setInterval

为什么尽量别用setInterval???

setInterval无视代码错误

setInterval有个讨厌的习惯,即对自己调用的代码是否报错这件事漠不关心,如果setInterval执行的代码由于某种原因出了错,它还会持续不断(不管不顾)地调用该代码。

function a() {
  try {
    cnosole.log('单词拼写错误,应该是console')
  } catch (e) {
    console.log('错误了')
  }
}
setInterval(a, 1000)

// 8VM69:5 错误了  (控制台每间隔一秒就会输出一个错误了) 
setInterval无视网络延迟

假设你每隔一段时间就通过Ajax轮询一次服务器,看看有没有新数据。而由于某些原因(服务器过载、临时断网、流量剧增、用户带宽受限,等等,你的请求要花的时间远比你想象的要长。但setInterval不在乎。它仍然会按定时持续不断地触发请求,最终你的客户端网络队列会塞满Ajax调用。

例子:下面代码并不是上一次fn执行完了之后再过100ms才开始执行下一次fn。 事实上,setInterval并不管上一次fn的执行结果,而是每隔100ms就将fn放入异步队列,而两次fn之间具体间隔多久就不一定了,跟setTimeout实际延迟时间类似,和JS执行情况有关。

(function testSetInterval() {
    let i = 0;
    const start = Date.now();
    const timer = setInterval(() => {
        i++;
        i === 2 && clearInterval(timer);
        console.log(`第${i}次开始`, Date.now() - start);
        for(let i = 0; i < 900000000; i++) {}
        console.log(`第${i}次结束`, Date.now() - start);
    }, 100);
})();

VM232:7 第1次开始 104
VM232:9 第1次结束 603
VM232:7 第2次开始 605
VM232:9 第2次结束 1106

虽然每次fn执行时间都很长,但下一次并不是等上一次执行完了再过100ms才开始执行的,实际上早就已经等在队列里了。
在fn被阻塞的时候,setInterval仍然在组织将来对回调函数的调用。 因此,当第一次fn函数调用结束时,已经有6次函数调用在等待执行。

处理可能的阻塞调用

最简单也是最容易控制的方案,是在回调函数内部使用setTimeout函数。

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