首页 > 代码库 > setTimeout
setTimeout
根据HTML 5标准,setTimeout推迟执行的时间,最少是5毫秒。如果小于这个值,会被自动增加到5ms。
每一个setTimeout在执行时,会返回一个唯一ID,把该ID保存在一个变量中,并传入clearTimeout,可以清除定时器。
在setTimeout内部,this绑定采用默认绑定规则,也就是说,在非严格模式下,this会指向window;而在严格模式下,this指向undefined。
一、用setTimeout代替setInterval
由于setInterval间歇调用定时器会因为在定时器代码未执行完毕时又向任务队列中添加定时器代码,导致某些间隔被跳过等问题,所以应使用setTimeout代替setInterval。
setTimeout(function myTimer() { /** * 需要执行的代码 * setTimeout会等到定时器代码执行完毕后才会重新调用自身(递归),记得给匿名函数添加一个函数名,以便调用自身。 */ setTimeout(myTimer, 1000);}, 1000);
这样做的好处是,在前一个定时器执行完毕之前,不会向任务队列中插入新的定时器代码,可以避免任何缺失的间隔,还可以保证在下一次定时器代码执行前,至少要等待指定的间隔,避免了连续执行。这个模式主要用于重复定时器。
// 代码段1,间歇性输出1到10let num = 0;let max = 10;setTimeout(function myTimer() { num++; console.log(num); if (num === max) { return; } setTimeout(myTimer, 500);}, 500);
// 代码段2,间歇性输出1到10setTimeout(function myTimer() { num++; console.log(num); if (num < max) { setTimeout(myTimer, 500); }}, 500);
二、在for循环中创建setTimeout定时器
1、根据事件循环和任务队列的原理,定时器通常在循环结束后才会加入到任务队列执行。
2、定时器是循环创建的。
3、定时器几乎是同时开始计时的。
4、定时器中的回调函数属于闭包,包含着对循环后全局变量i的引用。在块作用域和定时器外创建一个函数作用域时,此时不会查找全局作用域。
5、定时器的第二个参数不属于闭包的一部分,其值与循环i的值相同。
程序运行遵循同步优先,异步靠边,回调垫底。
// 代码段1,输出6个5for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000 * i);}console.log(i);
第1个5直接输出,1 秒之后,输出 5 个 5,并且每隔1s输出一个,一共用时4s。
for循环和循环体外部的console是同步的,所以先执行for循环,再执行外部的console.log。等for循环执行完,就会给setTimeout传参,最后执行。
JavaScript单线程如何处理回调呢?JavaScript同步的代码是在堆栈中顺序执行的,而setTimeout回调会先放到消息队列,for循环每执行一次,就会放一个setTimeout到消息队列排队等候,当同步的代码执行完了,再去调用消息队列的回调方法。这个消息队列执行的时间,需要等待到函数调用栈清空之后才开始执行。即所有可执行代码执行完毕之后,才会开始执行由setTimeout定义的操作。而这些操作进入队列的顺序,则由设定的延迟时间来决定,消息队列遵循先进先出(FIFO)原则。因此,即使我们将延迟时间设置为0,它定义的操作仍然需要等待所有代码执行完毕后才开始执行。这里的延迟时间,并非相对于setTimeout执行这一刻,而是相对于其他代码执行完毕这一刻。
先执行for循环,按顺序放了5个setTimeout回调到消息队列,然后for循环结束,下面还有一个同步的console,执行完console之后,堆栈中已经没有同步的代码了,就去消息队列找,发现找到了5个setTimeout,注意setTimeout是有顺序的。
JavaScript在把setTimeout放到消息队列的过程中,循环的i是不会及时保存进去的,相当于你写了一个异步的方法,但是ajax的结果还没返回,只能等到返回之后才能传参到异步函数中。
for循环结束之后,因为i是用var定义的,所以var是全局变量(这里没有函数,如果有就是函数内部的变量),这个时候的i是5,从外部的console输出结果就可以知道。那么当执行setTimeout的时候,由于全局变量的i已经是5了,所以传入setTimeout中的每个参数都是5。很多人都会以为setTimeout里面的i是for循环过程中的i,这种理解是不对的。
因此,即使我们将延迟时间设置为0,它定义的操作仍然需要等待所有代码执行完毕后才开始执行。这里的延迟时间,并非相对于setTimeout执行这一刻,而是相对于其他代码执行完毕这一刻。
for (var i = 0; i < 5; i++) { console.log(i); setTimeout(function myTimer() { console.log(i); }, i * 1000);}
立刻输出0 1 2 3 4
间歇输出5个5
温馨提示:如果在开发者工具console面板运行这段程序,你会看到不一样的结果。立刻输出0 1 2 3 4立即输出定时器ID间歇输出5个5
如何让该程序间歇输出0 1 2 3 4呢?
这里有两种思路,不过原理都相同。
思路1:ES6 let关键字,给setTimeout定时器外层创建一个块作用域。
for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000 * i);}
思路1的另一种表达
for (var i = 0; i < 5; i++) { let j = i; //闭包的块作用域 setTimeout(function() { console.log(j); }, 1000 * j);}
setTimeout