js中setTimeout与setInterval之魔法进阶篇

JavaScript/前端
705
0
0
2022-06-14

共同点

1.javascript的单线程:

function fn() { 
  setTimeout(function(){alert('can you see me?');},1000); //alert永远都不会弹出  
  while(true) {}
}

2.给定时器调用传递参数:

无论是window.setTimeout还window.setInterval,在使用函数名作为调用句柄时都不能带参数,我们可以使用字符串的形式达到想要的结果,如window.setTimeout("hello(userName)",3000);(如果强行使用传参函数,则会变成立即执行函数);但这种写法不够直观,而且有些场合必须使用函数名,下面用一个小技巧来实现带参数函数的调用:

<script language="JavaScript" type="text/javascript"> 
  //根据用户名显示欢迎信息  
  function hello(_name){
    alert("hello,"+_name);
  }
  //创建一个函数,用于返回一个无参数函数  
  function _hello(_name){
    return function(){
      hello(_name); 
    } 
  }
  window.setTimeout(_hello(userName),3000);
</script>

这里定义了一个函数_hello,用于接收一个参数,并返回一个不带参数的函数,在这个函数内部使用了外部函数的参数,从而对其调用,不需要使用参数。

异同点

1.setTimeout:如果setTimeout后面的代码执行时间很长,则setTimeout内的代码要等到setTimeout后面的代码执行完才能执行,虽然此时很可能已经超过了setTimeout设定的定时时间;

setInterval:如果setInterval内代码执行时间很超过了设定的定时时间,则等待队列中只能保留一个待执行的程序。

js中setTimeout与setInterval之魔法进阶篇

2.如果setTimeout函数的主体部分需要2秒钟执行完,定时时间为3秒,那么整个函数则要每5秒钟才执行一次。

setInterval却没有被自己所调用的函数所束缚,它只是简单地每隔一定时间就重复执行一次那个函数。如果要求在每隔一个固定的时间间隔后就精确地执行某动作,那么最好使setInterval,而如果不想由于连续调用产生互相干扰的问题,尤其是每次函数的调用需要繁重的计算以及很长的处理时间,那么最好使用setTimeout。

3. setTimeout递归执行的代码必须是上一次执行完了并间格一定时间才再次执行,比如说: setTimeout延迟时间为1秒执行, 要执行的代码需要2秒来执行,然后延迟一秒才能执行下一个setTimeout,那这段代码上一次与下一次的执行时间为3秒. 而不是我们想象的每1秒执行一次。

setInterval是排队执行的,比如说: setInterval每次延时时间为1秒,而执行的代码需要2秒执行,那它还是每次去执行这段代码,上次还没执行完的代码会排队,上一次执行完下一次的就立即执行,就没有那一秒的延时了,这样实际执行的间隔时间为2秒。

当定时器遇到闭包

1.总结与研究就是要多做demo,因为有的事情我们看起来很简单,真正做起来的时候不是那么一回事。比如如下:

for(var i = 1; i <= 3; i++) {
  setTimeout(function(){
    console.log(i); //连续输出3次4
  },100);
}

2.第一个参数是用来传递要调用的方法,可以传递一个代码串,如下:

<script> 
  function fn(value){
    alert("value=" + value);
  }
  setTimeout("fn(1)", 1000);
</script>

但是当在一个闭包里调用的时候,就会出现问题,如:

<script> 
  function outerFn(){
    var value = 1;
    function fn(){
      alert("value=" + value);
      value += 1;
    }
    setInterval("fn()", 3000);
  }
  outerFn();
</script>

会出现错误:Uncaught ReferenceError: fn is not defined

原因是fn()是以字符串的方式传递的,它的作用域是全局作用域,全局作用域是无法访问到fn()的。

解决的办法是fn以函数引用的方式传递,也就是setInterval()的第二种传参方式。

<script> 
  function outerFn(){
    var value = 1;  
    function fn(){
      alert("value=" + value);
      value += 1;
    }
    setInterval(fn, 3000);
  }
  outerFn();
</script>

但是这样又带来问题,如果想给fn传参数怎么办?可以像如下这样去写吗?

<script> 
  function outerFn(){
    var value = 1;
    function fn(n){
      alert("value=" + n);
    }
    setTimeout(fn(5), 1000);
  }
  outerFn();
</script>

答案是不可以的,函数只写函数名,是函数引用;后面加括号是函数执行。

1 setTimeout(fn, 1000); //fn的引用

2 setTimeout(fn(5), 1000); //fn直接执行

所以第7行,没有按照预期延迟1000毫秒执行fn(5),而是立刻就执行了。这要注意和上面第一种方式——传递代码字符串的不同。

如果确实有从外部传参的需要,该怎么办呢?

<script> 
  function outerFn(value){
    function fn(){
      alert("value=" + value);
    }
    setTimeout(fn, 1000);
  }
  outerFn(5);
</script>

如上,是利用了闭包的原理,fn作为内部函数,是可以访问包含它的outerFn的作用域中的变量的,因此我们想给fn传参,只要给outerFn传参就可以了。这在传递的参数复杂(比如是一个复杂的json)的情况下,很有用途。

定时器中的this

1.由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上. 这会导致,这些代码中包含的 this 关键字会指向 window (全局对象)对象,这和所期望的this的值是不一样的。setInterval的情况类似。

<script type="text/javascript"> 
  //this指向window  
  function shape(name) {
    this.name = name;
    this.timer = function(){
      alert('my shape is '+this.name)
    };
    setTimeout(this.timer, 50);
  }
  new shape('rectangle');
</script>

js中setTimeout与setInterval之魔法进阶篇

没有被传进去,分别用chrome,firefox和IE9实验了下,都是这个结果

解决方法一:

<script type="text/javascript"> 
  function shape(name) {
    this.name = name;
    this.timer = function(){
      alert('my shape is '+this.name)
    };
    var _this = this;
    setTimeout(function() {_this.timer.call(_this)}, 50);
  }
  new shape('rectangle');
</script>

设置一个局部变量_this,然后放到setTimeout的函数变量中,timer执行call或apply,设置this值。

function能够调用局部变量_this,多亏了Javascript的闭包。里面涉及了作用域链等知识,有兴趣的可以自己去了解下,这里不展开了

定时器中的事件

setTimeout:定时器对队列的工作方式:当特定时间过去后将代码添加到队列中,但并不意味着会马上将执行,设定一个200ms后执行的定时器,指的是在200ms后它将被添加到队列中,是否执行,还得看队列中是否没有其他的东西。看一下例子:

var a=document.getElementById("nav");
  a.onclick=function(){
  setTimeout(alertsomething,200);
  //一些其他的代码
}
function alertsomething(){
  alert("it is working");
}

假定onclick处理程序需要执行300ms,这时虽然在205ms添加了定时器代码,但是仍旧需要等待onclick事件完成后才能够执行。如图所示,本来在205ms处添加了定时器代码,但是由于此时onclick事件还没结束,故要等到300ms后才执行定时器代码。

js中setTimeout与setInterval之魔法进阶篇

setInterval:为了避免多个定时器代码不间断连续运行好几次,当使用setInterval(),仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中,通俗点就是等到上个定时器完成,再添加一个。

缺点:

1.某些间隔会被跳过

2.多个定时器的代码执行之间的间隔可能会比预期小。

js中setTimeout与setInterval之魔法进阶篇

在5处,创建一个定时器

205处,添加一个定时器,但是onclick代码没执行完成,等待

300处,onclick代码执行完毕,执行第一个定时器

405处,添加第二个定时器,但前一个定时器没有执行完成,等待

605处,本来是要添加第三个定时器,但是此时发现,队列中有了一个定时器,被跳过

等到第一个定时器代码执行完毕,马上执行第二个定时器,所以间隔会比预期的小。

解决方法:链式调用,主要用于重复定时器

setTimeout(function(){
  //处理代码  
  setTimeout(arguments.callee,interval)
},intercal);

递归调用自己。