JavaScript是单线程语言,但它允许通过设置超时值和间歇时间值来调度代码在特定的时刻执行。
setTimeout
setTimeout(function() {
alert('hello word')
}, 1000)
上述代码设置了等待一秒之后弹出警告框。setTimeout有两个参数,第一个是传递一个函数,第二个是毫秒数,在等待设置的毫秒数之后执行函数的操作。
但是在经过该时间后不一定会执行指定的函数代码。
原因是:JavaScript是一个单线程解释器,因此一定时间内只能执行一段代码。为了控制要执行的代码,就有一个JavaScript任务队列。这些任务会按照将它们添加到队列的顺序执行。setTimeout()的第二个参数告诉JavaScript再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行。
在调用setTimeout之后,该方法会返回一个数值ID,表示超时调用。这个超时调用ID是计划执行代码的唯一标识符,可以通过它来取消超时调用。要取消尚未执行的超时调用计划,可以调用clearTimeout方法并将ID作为参数传递给它
var timeoutId = setTimeout(function () {
alert('hello world')
})
clearTimeout(timeoutId)
setInterval
间歇调用与超时调用类似,只不过它会按照指定的时间间隔重复执行代码,直至间歇调用被取消或者页面被卸载。
setInterval(function () {
alert('hello world')
}, 1000)
调用setInterval()方法同样会返回一个间歇调用ID,该ID可用于在将来某个时刻取消间歇调用。要取消间歇调用可以使用clearInterval()方法并传入相应的间歇调用ID
var max = 10
var num = 0
var intervalId = setInterval(function () {
num++
if (num === max) { // 当num达到最大值时取消间歇调用
clearInterval(intervalId)
}
}, 1000)
有趣的面试题
for (var i = 1; i <=5; i++) {
setTimeout( function timer () {
console.log(i)
}, i * 1000);
}
// 要求改动上述代码,使其依次输出1,2,3,4,5
根据上面setTimeout的执行调用可知,JS是一个单线程解释器,一段时间只能执行一段代码,为了控制执行JS有自己的任务队列,这些任务会按照将它们添加到任务队列的顺序执行,而setTimeout中的时间参数是告诉JS多久之后将它添加到队列中。所以上面的setTimeout中要执行的函数,要等到for循环执行完成之后,再过一秒将setTimeout中的函数添加到任务队列中,然后执行。
直接执行上面的代码会每隔一秒钟输出一个6,因为到执行setTimeout中的函数时,i已经变成了6(因为循环里每次迭代同时共享着变量i,循环内部创建的函数全都保留了对相同变量的引用),所以会输出6。
要想实现不同的数字,就需要把每个定时器访问的变量独立起来,这就用到了JS中的闭包。
- 使用闭包来解决 - 闭包是指有权访问另一个函数作用域中的变量的函数
// 在setTimeout外添加一个立即调用函数表达式,将变量i作为参数传入闭包中
for (var i = 1; i <=5; i++) {
(function(value) {
setTimeout( function timer () {
console.log(i)
}, i * 1000);
}
)(i)
}
在循环内部,立即调用函数(IIFE)为接受的每一个变量i都创建了一个副本并存储为变量value。这个变量的值就是响应迭代创建的函数所使用的值,因此调用每个函数都会像从0-9循环一样得到期望的值。
- 使用块级作用域let来解决
for (let i = 1; i <= 5; i++) {
setTimeout( function timer () {
console.log(i)
}, i * 1000);
}
每次循环的时候let声明都会创建一个新的变量i,并将其初始化为i的当前值,所以循环内部创建的每个函数都能得到属于它们自己的i的副本。