setTimeout-and-setInterval

by Teobler on 10/07/2017

undefined views

这是一个老生常谈,新手掉坑的问题,算是一个比较经典的对于javascript运行机制的理解问题,由于好几次踩坑,决定好好理解下,我在这里粗浅的谈一下自己的理解,话不多说,进入正题:

两者表面上的区别

  1. setTimeout() 方法用于在指定毫秒数之后调用其中的函数

  2. setInterval() 方法则是在间隔一定毫秒后重复调用其中的函数

透过现象看本质

时间精确问题

由于js是运行在单线程的环境当中的,单线程就意味着任务的执行需要依赖任务队列。实际运行时是将两个方法的代码块移出当前运行环境(从任务队列移出到回调队列中),当执行完当前任务后,检查回调队列中有无需要执行的任务(对应这两个方法为是否已经到执行时间),可是如果时间到时恰好有别的任务在进行的话,由于其单线程的机制,该方法就只能等到当前任务结束之后才能运行。

回到方法本身,这就相当于其他的正常任务在一个队列中,当遇到这两个方法时,就将他们移出队列,并开始计时,当时间到时,直接“插队”到队首,如果队首有正在执行的任务,则排在次队首,等待执行。也就是说,这仅仅是“计划”在未来某一个时间执行某个任务,并不能保证精确的时间。

setInterval重复执行问题

这个方法执行时仅当没有该计时器的其他代码示例时才进行下一轮的执行。这样的规则就会导致某些间隔会被跳过,同时多个间隔可能比预期时间要短。所以为了避免setInterval所造成的问题,可以用setTimeout来通过循环代替setInterval方法,从而实现一个重复的定时器(除非必要,尽量避免代码中出现setInterval)

方法中使用this的问题

在两个方法中传入函数时(即第一个函数参数中含有另外一个函数),此函数中的this会只想window对象。这是由于两个方法调用的代码在与所在函数完全分离的执行环境上(第一条中有讲到的两个方法的运行机制),这就会导致这些代码中包含的this关键字会指向window(或全局)对象。

但是要注意,如果this只是在两个方法中而不是在方法中的函数中时,this的指向符合我们的预期为当前对象。

解决方法:

1.将当前对象的this存为一个变量,定时器内的函数利用闭包来访问这个变量,如下:

var num = 0;

function Obj (){

    var that = this;    //将this存为一个变量,此时的this指向obj

    this.num = 1,

    this.getNum = function(){

        console.log(this.num);

    },

    this.getNumLater = function(){

        setTimeout(function(){

            console.log(that.num);    //利用闭包访问that,that是一个指向obj的指针

        }, 1000)

    }

}

var obj = new Obj;

obj.getNum();          //1  打印的为obj.num,值为1

obj.getNumLater()      //1  打印的为obj.num,值为1

2.利用bind()方法:

var num = 0;

function Obj (){

    this.num = 1,

    this.getNum = function(){

        console.log(this.num);

    },

    this.getNumLater = function(){

        setTimeout(function(){

            console.log(this.num);

        }.bind(this), 1000)    //利用bind()将this绑定到这个函数上

    }

}

var obj = new Obj;

obj.getNum();                 //1  打印的为obj.num,值为1

obj.getNumLater()             //1  打印的为obj.num,值为1

bind()方法是在Function.prototype上的一个方法,当被绑定函数执行时,bind方法会创建一个新函数,并将第一个参数作为新函数运行时的this。在这个例子中,在调用setTimeout中的函数时,bind方法创建了一个新的函数,并将this传进新的函数,执行的结果也就是正确的了。关于bind方法可参考 MDN bind

清除计时器

clearTimeout()

在在使用setTimeout时,该方法会返回一个唯一的关于当前计时器的计时ID,在clearTimeout()方法中传入这个ID值即可取消对应的Timeout

clearInterval()

同上

参考

上面是我在使用过程中遇到问题后在网上查阅后自己的一些总结,希望对和我一样的新手有所帮助,其实还能透过这两个方法深入了解js的任务队列与调用栈,这里由于不是主题我就不深入讨论了,想要更深入了解他们的区别和js的一些运行机制,请入传送门:

传送门1—阮大的剖析

传送门2—this指向

传送门3—调用执行