# 深入定时器函数

# setTimeout

setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。

console.log(1);
var id = setTimeout("console.log(2)", 1000);
console.log(3);
console.log("id:" + id);
// 1
// 3
// id: 1;
// 2

如果推迟执行的是函数,就直接将函数名,作为setTimeout的参数。

setTimeout的第二个参数如果省略,则默认为 0。

除了前两个参数,setTimeout还允许更多的参数。它们将依次传入推迟执行的函数(回调函数)。

setTimeout(
  function (a, b) {
    console.log(a + b);
  },
  1000,
  1,
  1
);

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

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

# setInterval

setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。

# clearTimeout

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

# setTimeout(f, 0)

setTimeout的作用是将代码推迟到指定时间执行,如果指定时间为0,即setTimeout(f, 0),那么会立刻执行吗?

答案是不会。因为上一节说过,必须要等到当前脚本的同步任务,全部处理完以后,才会执行setTimeout指定的回调函数f。也就是说,setTimeout(f, 0)会在下一轮事件循环一开始就执行。

setTimeout(f, 0)有几个非常重要的用途。它的一大应用是,可以调整事件的发生顺序。比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,想让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)

由于setTimeout(f, 0)实际上意味着,将任务放到浏览器最早可得的空闲时段执行,所以那些计算量大、耗时长的任务,常常会被放到几个小部分,分别放到setTimeout(f, 0)里面执行。

# 运行机制

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

setInterval(function () {
  console.log(2);
}, 1000);
sleep(3000);
function sleep(ms) {
  var start = Date.now();
  while (Date.now() - start < ms) {}
}
//4秒钟后输出,2
//之后每秒钟输出,2

# debounce

​ 有时我们不希望回调函数被频繁调用。比如,用户填入网页输入框的内容,希望通过 Ajax 方法传回服务器,jQuery 的写法如下。

$("textarea").on("keydown", ajaxAction);

​ 当用户连续击键,就会连续触发 keydown 事件,造成大量的 Ajax 通信。这是不必要的,而且很可能产生性能问题。正确的做法是,设置一个门槛值,表示两次 Ajax 通信的最小间隔事件。

​ 这种做法叫做防抖动,假定防抖时间间隔为 2500ms,上面的代码可以改写成这样。

$("textarea").on("keydown", debounce(ajaxAction, 2500));
function debounce(fn, delay) {
  var timer = null; // 声明计时器
  return function () {
    var context = this;
    var args = arguments;
    clearTimeout(timer); //取消之前的定时器
    timer = setTimeout(function () {
      fn.apply(context, args); //定时结束,执行任务
    }, delay);
  };
}

上面代码中,用户再次击键,就会取消上一次的定时器,然后再新建一个定时器。

# throttle