setTimeout&&setInterval
有时候我们不想要马上执行函数,而是在未来的特定时间里执行,这就涉及到调度问题,Javascript提供了setTimeout和setInterval这两个函数:
(1)setTimeout:允许在指定的时间间隔之后执行函数(一次);
(2)setInterval:允许在运行期间定期地执行函数(多次);
setTimeout
setTimeout的语法如下:
let timerId = setTimeout(func|code, delay[, arg1, arg2...])
其中func|code需要执行的函数或字符串代码;delay是延迟运行的时间,以毫秒为单位;arg1,arg2...指定是被调度函数的参数。
延迟一秒后执行的例子:
function sayHi() {
alert('Hello');
}
setTimeout(sayHi, 1000);
设置执行函数参数:
function sayHi(phrase, who) {
alert( phrase + ', ' + who );
}
setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John
如果setTimeout的第一个参数是字符串代码,则Javascript默认根据代码生成一个函数,例如:
setTimeout("alert('Hello')", 1000);
但是使用字符串代码的形式是不推荐的,我们可以使用箭头函数来代替它,例如:
setTimeout(() => alert('Hello'), 1000);
需要注意的是,有时候我们可能会在执行函数后面加多个括号“()”,这样setTimeout函数会报错,例如:
// wrong!
setTimeout(sayHi(), 1000); //undefined
这是因为setTimeout接受的是一个函数的引用,引用的值会传递给setTimeout。但如果是一个函数的话,由于sayHi()不返回任何值,故没有传递值给setTimeout,所以结果是undefined
取消调度clearTimeout
我们使用setTimeout来设置调度后,也可以通过clearTimeout来取消调度,例如:
let timerId = setTimeout(...);
clearTimeout(timerId);
由于每次使用setTimeout后都会默认返回一个标识符,所以取消调度的会则要通过这个标识符
再看下面的例子:
let timerId = setTimeout(() => alert("never happens"), 1000);
alert(timerId); // timer identifier
clearTimeout(timerId);
alert(timerId); // same identifier (doesn't become null after canceling)
从上面的结果中可以看出,在浏览器中timerId是一个数字,而在其他环境中可能会是其他类型数据,例如在Node.js会返回一个附带其他方法的timer对象
setInterval
setInterval和setTimeout有一样的语法:
let timerId = setInterval(func|code, delay[, arg1, arg2...])
但是有一点不同的是setInterval会在时间间隔后定期执行,不是只有一次
为了取消setInterval,我们可以使用clearInterval,例如:
// repeat with the interval of 2 seconds
let timerId = setInterval(() => alert('tick'), 2000);
// after 5 seconds stop
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
递归的setTimeout
前面我们已经知道了,setTimeout仅仅执行一次,为了达到setInterval的效果,我们可以递归地使用setTimeout,例如:
/** instead of:
let timerId = setInterval(() => alert('tick'), 2000);
*/
let timerId = setTimeout(function tick() {
alert('tick');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);
相比于setInterval,递归使用setTimeout可能更加灵活,因为我们还可以在递归中修改delay时间
递归使用setTimeout严格保证了执行函数被调度的时间间隔,而setInterval却不能严格保证,看下面两个例子:
let i = 1;
setInterval(function() {
func(i);
}, 100);
let i = 1;
setTimeout(function run() {
func(i);
setTimeout(run, 100);
}, 100);
第一个例子中,func()会在每隔100毫秒后被调用,无论func()有没有执行完,若func()执行时间大于间隔时间,那么就会出现叠加调用的情况,即第一次调用的func()还没执行完,func()的第二次调用就开始了;第二个例子中,func()每次执行完后的100毫秒后再次被调用。因此,我们应该根据具体情况使用setInterval和递归的setInterval
setTimeout(…,0)情况
这里有一个特殊的情况,当使用setTimeout(...,0)时,它会在当前的其他代码执行完后再执行,也就是异步,例如:
setTimeout(() => alert("World"), 0);
alert("Hello");
这里它会先输出"Hello",再输出"World"