22_setTimeout和setInterval的运行机制了解吗

一、setTimeout()

1、基本用法

setTimeout()方法用来指定某个函数或字符串在指定的毫秒数之后执行。它返回一个整数,表示定时器的编号,这个值可以传递给clearTimeout()用于取消这个函数的执行

以下代码中,控制台先输出0,大概过1000ms即1s后,输出定时器setTimeout()方法的返回值1

var Timer = setTimeout(function(){
    console.log(Timer);
},1000);
console.log(0);

也可以写成字符串参数的形式,由于这种形式会造成javascript引擎两次解析,降低性能,故不建议使用

var Timer = setTimeout('console.log(Timer);',1000);
console.log(0);

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

以下代码中,控制台出现0和1

var Timer = setTimeout(function(){
    console.log(Timer);
});
console.log(0);
// 0
// 1

实际上,除了前两个参数,setTimeout()方法还允许添加更多的参数,它们将被传入定时器中的函数中

以下代码中,控制台大概过1000ms即1s后,输出2,而IE9-浏览器只允许setTimeout有两个参数,不支持更多的参数,会在控制台输出NaN

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

可以使用IIFE传参来兼容IE9-浏览器的函数传参

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

或者将函数写在定时器外面,然后函数在定时器中的匿名函数中带参数调用

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

[注意]IE8-浏览器不允许向定时器中传递事件对象event,如果要使用事件对象中的某些属性,可以将其保存在变量中传递进去

document.onclick = function(e){
    e = e || event;
    var type = e.type;
    setTimeout(function(){
        console.log(type);//click
        console.log(e.type);//报错
    })
}

2、this指向

注:定时器中的this容易存在隐式丢失的情况

var a = 0;
function foo(){
    console.log(this.a);
};
var obj = {
    a : 2,
    foo:foo
}
setTimeout(obj.foo,100);//0

等价于

var a = 0;
setTimeout(function foo(){
    console.log(this.a);
},100);//0

若想获得obj对象中的a属性值,可以将obj.foo函数放置在定时器中的匿名函数中进行隐式绑定

var a = 0;
function foo(){
    console.log(this.a);
};
var obj = {
    a : 2,
    foo:foo
}
setTimeout(function(){
    obj.foo();
},100);//2

或者也可以使用bind方法将foo()方法的this绑定到obj上

var a = 0;
function foo(){
    console.log(this.a);
};
var obj = {
    a : 2,
    foo:foo
}
setTimeout(obj.foo.bind(obj),100);//2

3、clearTimeout()

setTimeout函数返回一个表示计数器编号的整数值,将该整数传入clearTimeout函数,取消对应的定时器

//过100ms后,控制台输出setTimeout()方法的返回值1
var Timer = setTimeout(function(){
    console.log(Timer);
},100);

于是可以利用这个值来取消对应的定时器

var Timer = setTimeout(function(){
    console.log(Timer);
},100);
clearTimeout(Timer);

或者直接使用返回值作为参数

var Timer = setTimeout(function(){
    console.log(Timer);
},100);
clearTimeout(1);

一般来说,setTimeout返回的整数值是连续的,也就是说,第二个setTimeout方法返回的整数值比第一个的整数值大1

//控制台输出1、2、3
var Timer1 = setTimeout(function(){
    console.log(Timer1);
},100);
var Timer2 = setTimeout(function(){
    console.log(Timer2);
},100);
var Timer3 = setTimeout(function(){
    console.log(Timer3);
},100);

二、setInterval()

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

<button id="btn">0</button>
<script>
var btn = document.getElementById('btn');
var timer = setInterval(function(){
    btn.innerHTML = Number(btn.innerHTML) + 1;
},1000);
btn.onclick = function(){
    clearInterval(timer);
    btn.innerHTML = 0;
}
</script>

[注意]HTML5标准规定,setTimeout的最短时间间隔是4毫秒;setInterval的最短间隔时间是10毫秒,也就是说,小于10毫秒的时间间隔会被调整到10毫秒

大多数电脑显示器的刷新频率是60HZ,大概相当于每秒钟重绘60次。因此,最平滑的动画效的最佳循环间隔是1000ms/60,约等于16.6ms

为了节电,对于那些不处于当前窗口的页面,浏览器会将时间间隔扩大到1000毫秒。另外,如果笔记本电脑处于电池供电状态,Chrome和IE10+浏览器,会将时间间隔切换到系统定时器,大约是16.6毫秒

三、运行机制

1、原理解析

解释下面代码,为什么0会出现在1的前面?

setTimeout(function(){
    console.log(1);
});
console.log(0);
// 0
// 1

实际上,把setTimeout的第二个参数设置为0s,并不是立即执行函数的意思,只是把函数放入异步队列。浏览器先执行完同步队列里的任务,才会去执行异步队列中的任务。

在下面这个例子中,给一个按钮btn设置了一个事件处理程序。事件处理程序设置了一个250ms后调用的定时器。点击该按钮后,首先将onclick事件处理程序加入队列。该程序执行后才设置定时器,再有250ms后,指定的代码才被添加到队列中等待执行。

btn.onclick = function(){
    setTimeout(function(){
        console.log(1);
    },250);
}

如果上面代码中的onclick事件处理程序执行了300ms,那么定时器的代码至少要在定时器设置之后的300ms后才会被执行。队列中所有的代码都要等到javascript进程空闲之后才能执行,而不管它们是如何添加到队列中的

如图所示,尽管在255ms处添加了定时器代码,但这时候还不能执行,因为onclick事件处理程序仍在运行。定时器代码最早能执行的时机是在300ms处,即onclick事件处理程序结束之后

2、setInterval()的问题

使用setInterval()的问题在于,定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次,而之间没有任何停顿。而javascript引擎对这个问题的解决是:当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。这确保了定时器代码加入到队列中的最小时间间隔为指定间隔

但是,这样会导致两个问题:1、某些间隔被跳过;2、多个定时器的代码执行之间的间隔可能比预期的小

假设,某个onclick事件处理程序使用setInterval()设置了200ms间隔的定时器。如果事件处理程序花了300ms多一点时间完成,同时定时器代码也花了差不多的时间,就会同时出现跳过某间隔的情况

例子中的第一个定时器是在205ms处添加到队列中的,但是直到过了300ms处才能执行。当执行这个定时器代码时,在405ms处又给队列添加了另一个副本。在下一个间隔,即605ms处,第一个定时器代码仍在运行,同时在队列中已经有了一个定时器代码的实例。结果是,在这个时间点上的定时器代码不会被添加到队列中

3、迭代setTimeout

为了避免setInterval()定时器的问题,可以使用链式setTimeout()调用。

setTimeout(function fn(){
    setTimeout(fn,interval);
},interval);

这个模式链式调用了setTimeout(),每次函数执行的时候都会创建一个新的定时器。第二个setTimeout()调用当前执行的函数,并为其设置另外一个定时器。这样做的好处是,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。而且,它可以保证在下一次定时器代码执行之前,至少要等待指定的间隔,避免了连续的运行。

4、使用setInterval()

<div id="myDiv" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div>
<script>
var myDiv = document.getElementById('myDiv'); 
myDiv.onclick = function(){
    var timer = setInterval(function(){
        if(parseInt(myDiv.style.left) > 200){
            clearInterval(timer);
            return false;
        }
        myDiv.style.left = parseInt(myDiv.style.left) + 5 + 'px';    

    },16);    
}
</script>

5、使用链式setTimeout()

<div id="myDiv" style="height: 100px;width: 100px;background-color: pink;position:absolute;left:0;"></div>
<script>
var myDiv = document.getElementById('myDiv'); 
myDiv.onclick = function(){
    setTimeout(function fn(){
        if(parseInt(myDiv.style.left) <= 200){
            setTimeout(fn,16);    
        }else{
            return false;
        }
        myDiv.style.left = parseInt(myDiv.style.left) + 5 + 'px';    
    },16);    
}
</script>

四、应用

1、场景

通常使用定时器来调整事件发生顺序

2、实例

(1)网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,我们先让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)

正常情况下,点击div元素,先弹出0,再弹出1

<div id="myDiv" style="height: 100px;width: 100px;background-color: pink;"></div>
<script>
myDiv.onclick = function(){
    alert(0);
}
document.onclick = function(){
    alert(1);
}
</script>

如果进行想让document的onclick事件先发生,即点击div元素,先弹出1,再弹出0。则进行如下设置

<div id="myDiv" style="height: 100px;width: 100px;background-color: pink;"></div>
<script>
myDiv.onclick = function(){
    setTimeout(function(){
        alert(0);
    })
}
document.onclick = function(){
    alert(1);
}
</script>

(2)用户自定义的回调函数,通常在浏览器的默认动作之前触发。比如,用户在输入框输入文本,keypress事件会在浏览器接收文本之前触发。因此,下面的回调函数是达不到目的

<input type="text" id="myInput">
<script>
myInput.onkeypress = function(event) {
  this.value = this.value.toUpperCase();
}
</script>

上面代码想在用户输入文本后,立即将字符转为大写。但是实际上,它只能将上一个字符转为大写,因为浏览器此时还没接收到文本,所以this.value取不到最新输入的那个字符

只有用setTimeout改写,上面的代码才能发挥作用

<input type="text" id="myInput">
<script>
myInput.onkeypress = function(event) {
    setTimeout(function(){
        myInput.value = myInput.value.toUpperCase();
    });
}
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容