共同点
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内代码执行时间很超过了设定的定时时间,则等待队列中只能保留一个待执行的程序。
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>
没有被传进去,分别用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后才执行定时器代码。
setInterval:为了避免多个定时器代码不间断连续运行好几次,当使用setInterval(),仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中,通俗点就是等到上个定时器完成,再添加一个。
缺点:
1.某些间隔会被跳过
2.多个定时器的代码执行之间的间隔可能会比预期小。
述
在5处,创建一个定时器
205处,添加一个定时器,但是onclick代码没执行完成,等待
300处,onclick代码执行完毕,执行第一个定时器
405处,添加第二个定时器,但前一个定时器没有执行完成,等待
605处,本来是要添加第三个定时器,但是此时发现,队列中有了一个定时器,被跳过
等到第一个定时器代码执行完毕,马上执行第二个定时器,所以间隔会比预期的小。
解决方法:链式调用,主要用于重复定时器
setTimeout(function(){
//处理代码
setTimeout(arguments.callee,interval)
},intercal);
递归调用自己。