关于定时器setTimeout和setInterval的简单总结

181 阅读4分钟
  • 定时器种类及定义

    • setTimeout

      • 解析:在定时器到期后执行一个函数或指定的一段代码
      • 语法:
        • var timeoutID = scope.setTimeout(function[, delay, arg1, arg2, ...]);
        • var timeoutID = scope.setTimeout(function[, delay]);
        • var timeoutID = scope.setTimeout(code[, delay]);
    • setInterval

      • 解析:重复调用一个函数或执行一个代码片段,在每次调用之间具有固定的时间间隔
      • 语法:
        • var timeoutID = scope.setTimeout(function[, delay, arg1, arg2, ...]);
        • var timeoutID = scope.setTimeout(function[, delay]);
        • var timeoutID = scope.setTimeout(code[, delay]);
    • 返回值 timeoutID

      • 返回值timeoutID是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时器。
      • 需要注意的是setTimeout()和setInterval()共用一个编号池,技术上,clearTimeout()和 clearInterval() 可以互换。但是,为了避免混淆,不要混用取消定时函数。
      • 在同一个对象上(一个window或者worker),setTimeout()或者setInterval()在后续的调用不会重用同一个定时器编号。但是不同的对象使用独立的编号池。
  • 常见问题

    • setTimeout和setInterval怎么传参
      • setTimeout和setInterval从第三个参数开始后面都是回调执行函数的参数
      setTimeout((...arguments)=>{
          console.log('接收到了参数',[...arguments]) // ['a','b','c','d']
      },1000,'a','b','c','d')
      
    • setTimeout和setInterval为什么不常用字符串形式
      • 向setTimeout()传递一个字符串而不是函数会遭受到与使用eval一样的风险.【字符串会在全局作用域内被解释执行,所以当setTimeout()函数执行完毕后,字符串中的变量不可用.】
      • 注意字符串时,该字符串是要被解析执行的,所以不能是函数名,必须是函数执行【fn ()
       * 推荐 *
       setTimeout(fn,1000)
      
       * 不推荐 *
       setTimeout(`fn()`,1000)
       function fn(){console.log(222)}
      
       * 推荐 *
       window.setTimeout(function() {
          alert("Hello World!");
       }, 500);
      
       * 不推荐 *
       window.setTimeout("alert(\"Hello World!\");", 500);
      
    • 浏览器对setTimeout和setInterval的极限支持时间范围是多少【最大和最小】,低于最小值时会怎样?超出最大值溢出后会怎样?
      • setTimeout 嵌套最少间隔4毫秒【所以,写0和写4没区别】
        • 最小延时 >=4ms
        • setTimeout(cb, 0); function cb() {setTimeout(cb, 0)}, 在Chrome 和 Firefox中, 定时器的第5次调用被阻塞了;在Safari是在第6次
          • 其实在前 5 次执行中时间间隔会是 0 毫秒,后面每次的调用最小时间间隔是 4 毫秒。之所以出现这样的情况,是因为在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒
      • 浏览器其内部以32个bit整数存储延时值的。32bit最大只能存放的数字是2147483647,这就会导致如果一个延时(delay)大于 2147483647 毫秒 (大约24.8 天)时就会溢出。【导致定时器将会被立即执行】
          * 溢出会立即执行 *
          function fn() {
              setTimeout(() => {
                  console.log('go')
              }, 2147483648)
          }
      
    • setTimeout和setInterval的this指向什么问题
      • setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。
      • 这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined,这和所期望的this的值是不一样的。
          let myArray = ["zero", "one", "two"];
          myArray.myMethod = function (sProperty) {
              alert(arguments.length > 0 ? this[sProperty] : this);
          };
      
          setTimeout(myArray.myMethod, 1000); // prints "[object Window]" 
          setTimeout(myArray.myMethod, 1000, "1"); // prints "undefined" 
      
          <!-- 破解方案 -->
          // 箭头函数
          setTimeout(() => {myArray.myMethod()}, 1000); // prints "zero,one,two",this now is 'myArray'
          setTimeout(() => {myArray.myMethod('1')}, 1000); // prints "one" ,this now is 'myArray'
      
          // 包装函数
          setTimeout(function(){myArray.myMethod()}, 1000); // prints "zero,one,two",this now is 'myArray'
          setTimeout(function(){myArray.myMethod('1')}, 1000); // prints "one" ,this now is 'myArray'
      
    • setTimeout和setInterval与闭包的那点事
      • 闭包是很容易照成内存泄露的一种场景,因为变量得不到释放
      • setTimeout是宏任务,在任务栈中最后执行
      • 经典的循环闭包题【破解方案中利用闭包让变量不被清除覆盖,缓存当时的变量值,只要当宏任务执行的时候取到对应上下文中的变量】
          for (var i = 0; i < 3; i++) {
              setTimeout(function() {
                  console.log(i);// 3,3,3
              }, 1000);
          }
      
          <!-- 破解方案 -->
          for (let i = 0; i < 3; i++) {
              setTimeout(function() {
                  console.log(i);// 1,2,3
              }, 1000);
          }
      
          for (var i = 0; i < 3; i++) {
              (function(i) {
                  setTimeout('console.log(' + i + ')', 1000 * i);
              })(i);
          }
          或者
          for (var i = 1; i <= 3; i++) {
              setTimeout((function(i) {
                  return function() {
                  console.log(i);
                  };
              })(i), 1000 * i);
          }
      
    • setTimeout和setInterval为什么时间不准,和单线程微宏任务有什么关系?
      • setTimeout 什么时候执行不取决于设置的时间
      • setTimeout和setInterval是宏任务,得等到微任务执行完,才能排队执行宏任务,若前一个宏任务一直执行没有执行完,下一个宏任务就一直在队列挂起状态
      • 函数的第二个参数是要等待的毫秒数,而不是要执行代码的确切时间。JavaScript是单线程的,所以每次只能执行一段代码。为了调度不同代码的执行,JavaScript维护了一个任务队列。其中的任务会按照添加到队列的先后顺序执行。setTimeout()的第二个参数只是告诉JavaScript引擎在指定的毫秒数过后把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待前面的任务执行完才能执行。
    • setInterval为什么在生产中不常用
      • 会跳帧,时间不准
        • setInterval()在实践中很少会在生产环境下使用,因为一个任务结束和下一个任务开始之间的时间间隔是无法保证的,有些循环定时任务可能会因此而被跳过。而像前面这个例子中一样使用setTimeout()则能确保不会出现这种情况。一般来说,最好不要使用setInterval()
        • 如下面的例子
            function click() {
                setInterval(function() { 
                    // process ... 
                    // 假如这里面是个长耗时的代码片段,假设耗时3s
                }, 1000); 
            }
            // 当点击启动循环定时器以后,第一次1s后执行process,第2s的时候执行第二次,第3s的时候执行第三次,第4s的时候执行第四次,
            // 但是第一次的process执行完需要3s,所以当第一次process执行完以后,队列里面已经有了第二次的和第三次的,但是js引擎只允许有一份未执行的process代码,所以会把第二次的抛弃
            // 由此可见,会跳帧
            // setTimeout就不会有这样的问题,他会在复合条件时执行,不会像setInterval时不停的执行,所以一般生产上会用setTimeout模拟setInterval
            // 尤其当循环请求接口的场景更要避免setInterval【requestanimationframe方案,这里不在展开】
        
      • 易内存泄漏
        • 定时器也可能会悄悄地导致内存泄漏。下面的代码中,定时器的回调通过闭包引用了外部变量,只要定时器一直运行,回调函数中引用的name就会一直占用内存。垃圾回收程序当然知道这一点,因而就不会清理外部变量,变量得不到释放,很容易内存泄露【使用JavaScript闭包很容易在不知不觉间造成内存泄漏】
            let name = 'Jake';
            setInterval(() => {  
                console.log(name);
            }, 1000);
        
      • 所以生产环境一般遇到需要重复执行的场景,一般用setTimeout模拟
    • 怎么样setTimeout模拟setInterval,且可清除
      • 两个简单的封装
      let timeoutID = null;
      timeoutID = setTimeout(fn, time);
      function fn(){
          if(xxx){//在条件范围内循环执行
              timeoutID = setTimeout(fn, time); 
          }else{//超出条件,直接清除
              clearTimeout(timeoutID)
          }
      }
      
      let timeoutID = null;
      timeoutID = setTimeout(function(){ 
          if(xxx){//在条件范围内循环执行
              timeoutID = setTimeout(arguments.callee, time); 
          }else{//超出条件,直接清除
              clearTimeout(timeoutID)
          }
      }, time);
      
    • setTimeout和setInterval有什么不同
      • setInterval除非主动清除或者页面关闭,否则不会停止
      • setTimeout页面关闭会停止