建立定时器,程序会被挂起,时间结束时把任务添加到 JavaScript 进程栈中,又由于 JavaScript 是单线程,必须等待前面的代码执行结束,因此定时器执行函数总会比设定时间要晚。
javascript
基本用法:html
setTimeout(() => {
// 处理代码 ...
}, timeout);复制代码
与 "setTimeout" 相似,区别在于 "setInterval" 会按期向 JavaScript 进程栈添加任务,但会有几个问题发生。
java(1) 某些间隔会被跳过;
编程(2) 多个定时器的代码执行之间的间隔可能比设定值要小。
数组(3) 前一个定时器代码未执行,当前定时器代码则被跳过。
浏览器
先介绍基本用法:闭包
setInterval(() => {
// 每隔一段时间执行 ...
}, interval);复制代码
引用《JavaScript高级编程》的一个图:函数
"onclick"时间处理函数内添加一个 "setInterval" 重复定时器,且给定了 200ms 的间隔,而定时器代码须要 300ms+ 才能执行完,但 "onclick" 处理函数内还有其余代码须要执行,耗时 300ms。动画
那么第一个定时器代码执行与 300ms 处,第二个则执行与 600ms+ 处,而 605ms 处本该被添加的定时器则被跳过,由于第二个定时器代码被添加却未被执行。ui
代码以下:
setTimeout(() => {
// 处理代码 ....
setTimeout(arguments.callee, interval);
}, interval);复制代码
但上述代码存在必定问题,由于严格模式下,没法使用 "arguments.callee" 得到函数自己。建议把处理函数具名化,以下:
setTimeout(function process() {
// 处理代码 ...
setTimeout(process, 1000);
}, 1000);复制代码
运行在浏览器中的 JavaScript 都被分配了一个肯定数量的资源。若是代码运行超过特定时间或者特定语句数量就不会继续执行。过长、嵌套过深的函数调用或者是进行大量处理的循环都是形成脚本时间运行过长的主要缘由。
for (let i = 0, len = data.length; i < len; i++) {
// 假设处理须要 200ms
process(data[i])
}复制代码
要改善上述状况,先要符合两个如下条件,则能够使用 "数组分块" 技术:
setTimeout(function timeoutProcess() {
// 取出下一个条目并处理,data:<Array>
let item = data.shift();
process(item);
// 判断是否还有条目,有则设置另外一个定时器
if (arr.length > 0) {
setTimeout(timeoutProcess, 100);
}
}, 100);复制代码
某些高频操做是不必的,如用户连续点击某个开关,致使请求屡次发送。则能够使用 "函数防抖" ,等待用户操做停下后片刻再发送请求。
"函数防抖" 的原理是利用定时器延迟执行,当重复执行时,先把本来定时器清除,再添加延迟执行代码,那么老是最后一次操做后延时执行代码。
核心代码:
// 防抖函数,两个参数:被防抖的函数,函数执行的上下文(忽略则为全局)
function debounce(methods, context){
clearTimeout(methods.timerId);
// 对方法添加一个属性 timerId ,存放定时器
methods.timerId = setTimeout(() => {
methods.call(context);
}, 100);
}复制代码
完整实例:(以 click 事件为例)
// <button id="btn">test</button> // 按钮
// 代码:
function debounce(fn, delay) {
// 经过闭包形式,与定时器对象关联
let timer = null;
return function () {
// 将参数和上下文传递给实际执行的函数
let context = this,
args = arguments;
if (timer) {
clearTimeout(timer);
timer = null
}
timer = setTimeout(() => {
fn.call(this, args, '给防抖函数的额外参数')
}, delay);
}
}
// 实际处理函数,以打印为例
function printFn(args, myArgs) {
console.log('do it!', ...args, myArgs)
}
// 以click事件为例,只有最后一次单击才生效
document.getElementById('btn').addEventListener('click', debounce(printFn, 1000))
复制代码
当咱们以必定频率处理某些频发事件,咱们能够使用"函数节流"。
"函数节流" 的原理是利用定时器延迟执行,当重复执行时,忽略新添加的处理函数。
核心代码:
// 节流函数,两个参数:被节流的函数,函数执行的上下文(忽略则为全局)
function throttle(methods, context) {
// 函数不含 timerId 时添加定时器对象,定时器对象存在期间不重复添加
if (!methods.timerId) {
methods.timerId = setTimeout(() => {
methods.timerId = null;
methods.call(context);
}, 1000);
}
}
复制代码
完整实例:(以 click 事件为例)
// <button id="btn">test</button> // 按钮
// 代码:
function throttle(fn, delay) {
// 经过闭包形式,与定时器对象关联
let timer = null;
return function () {
let context = this,
args = arguments;
// 当定时器存在时,则不操做,不然添加延时函数
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.call(this, args, '给防抖函数的额外参数')
}, delay);
}
}
}
// 实际处理函数,以打印为例
function printFn(args, myArgs) {
console.log('do it!', ...args, myArgs)
}
// 以click事件为例,连续点击的状况下,每隔一秒生效一次
document.getElementById('btn').addEventListener('click', throttle(printFn, 1000))复制代码
requestAnimationFrame
是HTML5新增的定时器。用法与setTimeout
相似,都是在一段时间后执行回调函数,区别在于不用传第二个时间参数。
执行回调函数的时延是根据显示器刷新率决定的,例如刷新率为 60Hz 的显示器,时延为1000ms/60
,约等于16.6ms
。执行回调函数的时间是比较准确的(众所周知,setTimeout
和setInterval
的执行回调函数时间并不许确)。
使用cancelAnimationFrame()
方法能够取消requestAnimationFrame()
定时器。
引用其余博主的一个例子:(进度条动画)
<div id="myDiv" style="background:lightblue;width:0;height:20px;line-height:20px;">0%</div> <button id="btn">run</button>复制代码
let btn = document.getElementById('btn'),
myDiv = document.getElementById('myDiv'),
timer;
btn.onclick = function () {
myDiv.style.width = '0'; cancelAnimationFrame(timer);
timer = requestAnimationFrame(function fn() {
if (parseInt(myDiv.style.width) < 500) {
myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
myDiv.innerHTML = parseInt(myDiv.style.width) / 5 + '%';
timer = requestAnimationFrame(fn);
} else {
cancelAnimationFrame(timer);
}
});
} 复制代码