定时器

作者: zooeydotmango | 来源:发表于2019-08-25 02:44 被阅读0次

    setTimeout()

    var timerId = setTimeout(func|code, delay);
    

    func|code是将要推迟执行的函数名或者一段代码,第二个参数delay是推迟执行的毫秒数,除了前两个参数,setTimeout还允许更多的参数。它们将依次传入推迟执行的函数(回调函数)。

    还有一个需要注意的地方,如果回调函数是对象的方法,那么setTimeout使得方法内部的this关键字指向全局环境,而不是定义时所在的那个对象。

    var x = 1;
    
    var obj = {
      x: 2,
      y: function () {
        console.log(this.x);
      }
    };
    
    setTimeout(obj.y, 1000) // 1
    

    为了防止出现这个问题,一种解决方法是将obj.y放入一个函数

    var x = 1;
    
    var obj = {
      x: 2,
      y: function () {
        console.log(this.x);
      }
    };
    
    setTimeout(function () {
      obj.y();
    }, 1000);
    // 2
    

    另一种解决方法是,使用bind方法,将obj.y这个方法绑定在obj上面。

    var x = 1;
    
    var obj = {
      x: 2,
      y: function () {
        console.log(this.x);
      }
    };
    
    setTimeout(obj.y.bind(obj), 1000)
    // 2
    

    setInterval()

    setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。
    setInterval的一个常见用途是实现轮询。下面是一个轮询 URL 的 Hash 值是否发生变化的例子。

    var hash = window.location.hash;
    var hashWatcher = setInterval(function() {
      if (window.location.hash != hash) {
        updatePage();
      }
    }, 1000);
    

    setInterval指定的是“开始执行”之间的间隔,并不考虑每次任务执行本身所消耗的时间。因此实际上,两次执行之间的间隔会小于指定的时间。比如,setInterval指定每 100ms 执行一次,每次执行需要 5ms,那么第一次执行结束后95毫秒,第二次执行就会开始。如果某次执行耗时特别长,比如需要105毫秒,那么它结束后,下一次执行就会立即开始。

    为了确保两次执行之间有固定的间隔,可以不用setInterval,而是每次执行结束后,使用setTimeout指定下一次执行的具体时间。

    var i = 1;
    var timer = setTimeout(function f() {
      // ...
      timer = setTimeout(f, 2000);
    }, 2000);
    

    clearTimeout(),clearInterval()

    返回一个整数值,表示计数器编号。将该整数传入clearTimeout和clearInterval函数,就可以取消对应的定时器。

    var id1 = setTimeout(f, 1000);
    var id2 = setInterval(f, 1000);
    
    clearTimeout(id1);
    clearInterval(id2);
    

    setTimeout和setInterval返回的整数值是连续的,也就是说,第二个setTimeout方法返回的整数值,将比第一个的整数值大1。

    function f() {}
    setTimeout(f, 1000) // 10
    setTimeout(f, 1000) // 11
    setTimeout(f, 1000) // 12
    

    利用这一点,可以写一个函数,取消当前所有的setTimeout定时器。

    (function() {
      // 每轮事件循环检查一次
      var gid = setInterval(clearAllTimeouts, 0);
    
      function clearAllTimeouts() {
        var id = setTimeout(function() {}, 0);
        while (id > 0) {
          if (id !== gid) {
            clearTimeout(id);
          }
          id--;
        }
      }
    })();
    

    debounce 函数/节流函数

    function throttle(fn, delay) {
        var timer = null
        return function(){
            clearTimeout(timer)
            timer = setTimeout(function(){ 
                fn(arguments)
            }, delay)
        }
    }
    
    function fn(){
        console.log('hello ')
    }
    
    var fn2 = throttle(fn, 1000)
    fn2()
    fn2()
    fn2()
    

    在上述函数中,只要在1000毫秒内再次执行fn2就会取消上一次的定时器,然后新建一个定时器。这样保证了回调函数之间的调用间隔。

    运行机制

    将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。

    这意味着,setTimeout和setInterval指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。

    单线程与任务队列

    单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。

    JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。

    首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。

    异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。

    JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。

    相关文章

      网友评论

          本文标题:定时器

          本文链接:https://www.haomeiwen.com/subject/moicectx.html