JavaScript 提供定时执行代码的功能,叫做定时器(timer),主要由 setTimeout()和 setInterval()这两个函数来完成。它们向任务队列添加定时任务
setTimeout()
setInterval()
clearTimeout(),clearInterval()
实例:debounce 函数
运行机制
setTimeout(f, 0)
含义
应用
1.setTimeout()
执行多少毫秒后执行,返回一个编号(顺序递增),用于取消。
第一个参数 func|code 是将要推迟执行的函数名或者一段代码,第二个参数 delay 是推迟执行的毫秒数。后面的参数为传给回调函数的参数
setTimeout(function (a,b) {
console.log(a + b);
}, 1000, 1, 1);
This(回调函数为对象方法)为全局
var x = 1;
var obj = {
x: 2,
y: function () {
console.log(this.x);
}
};
setTimeout(obj.y, 1000) // 1
上面代码输出的是 1,而不是 2。因为当 obj.y 在 1000 毫秒后运行时,this 所指向的已经不是 obj 了,而是全局环境。
为了防止出现这个问题,一种解决方法是将 obj.y 放入一个函数。
var x = 1;
var obj = {
x: 2,
y: function () {
console.log(this.x);
}
};
setTimeout(function () {
obj.y();
}, 1000);
// 2
上面代码中,obj.y 放在一个匿名函数之中,这使得 obj.y 在 obj 的作用域执行,而不是在全局作用域内执行,所以能够显示正确的值。
另一种解决方法是,使用 bind 方法,将 obj.y 这个方法绑定在 obj 上面。
var x = 1;
var obj = {
x: 2,
y: function () {
console.log(this.x);
}
};
setTimeout(obj.y.bind(obj), 1000)
//
2.setInterval()
setInterval 指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。
下面是一个通过 setInterval 方法实现网页动画的例子。
var div = document.getElementById(‘someDiv’);
var opacity = 1;
var fader = setInterval(function() {
opacity -= 0.1;
if (opacity >= 0) {
div.style.opacity = opacity;
} else {
clearInterval(fader);
}
}, 100);
上面代码每隔 100 毫秒,设置一次 div 元素的透明度,直至其完全透明为止。
setInterval 的一个常见用途是实现轮询。下面是一个轮询 URL 的 Hash 值是否发生变化的例子。
var hash = window.location.hash;
var hashWatcher = setInterval(function() {
if (window.location.hash != hash) {
updatePage();
}
}, 1000);
时间
不考虑执行时间即会小于 100ms,第二次执行就会开始。如果某次执行耗时特别长,比如需要 105 毫秒,那么它结束后,下一次执行就会立即开始。
为了确保两次执行之间有固定的间隔,可以不用 setInterval,而是每次执行结束后,使用 setTimeout 指定下一次执行的具体时间。
var i = 1;
var timer = setTimeout(function f() {
// …
timer = setTimeout(f, 2000);
}, 2000);
上面代码可以确保,下一次执行总是在本次执行结束之后的 2000 毫秒开始。
3.clearTimeout(),clearInterval()
利用这一点,可以写一个函数,取消当前所有的 setTimeout 定时器。
(function() {
// 每轮事件循环检查一次
var gid = setInterval(clearAllTimeouts, 0);
function clearAllTimeouts() {
var id = setTimeout(function() {}, 0);
while (id > 0) {if (id !== gid) {clearTimeout(id);
}
id--;
}
}
})();
上面代码中,先调用 setTimeout,得到一个计算器编号,然后把编号比它小的计数器全部取消
4. 实例:debounce 函数
debounce(防抖动)监听时间时(Keypress,一直触发函数。
有时,我们不希望回调函数被频繁调用。比如,用户填入网页输入框的内容,希望通过 Ajax 方法传回服务器,jQuery 的写法如下。
$(‘textarea’).on(‘keydown’, ajaxAction);
$(‘textarea’).on(‘keydown’, debounce(ajaxAction, 2500));
function debounce(fn, delay){
var timer = null; // 声明计时器
return function() {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {fn.apply(context, args);
}, delay);
};
}
上面代码中,只要在 2500 毫秒之内,用户再次击键,就会取消上一次的定时器,然后再新建一个定时器。这样就保证了回调函数之间的调用间隔,至少是 2500 毫秒。
5. 运行机制
将指定的代码移到下一轮事件循环,等这轮轮完,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。
回调函数必须等本轮运行完,因此时间不确定。
setTimeout(someTask, 100);
veryLongTask();
上面代码的 setTimeout,指定 100 毫秒以后运行一个任务。但是,如果后面的 veryLongTask 函数(同步任务)运行时间非常长,过了 100 毫秒还无法结束,那么被推迟运行的 someTask 就只有等着,等到 veryLongTask 运行结束,才轮到它执行
6.setTimeout(f, 0)
6.1 含义
因为上一节说过,必须要等到当前脚本的同步任务,全部处理完以后,才会执行 setTimeout 指定的回调函数 f
setTimeout(function () {
console.log(1);
}, 0);
console.log(2);
// 2
// 1
上面代码先输出 2,再输出 1。因为 2 是同步任务,在本轮事件循环执行,而 1 是下一轮事件循环执行。
setTimeout(f, 0)会在下一轮事件循环一开始就执行
6.2 应用
6.2.1 它的一大应用是,可以调整事件的发生顺序。
比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,想让父元素的事件回调函数先发生,就要用到 setTimeout(f, 0)。
// HTML 代码如下
// <input type=”button” id=”myButton” value=”click”>
var input = document.getElementById(‘myButton’);
input.onclick = function A() {
setTimeout(function B() {
input.value +='input';
}, 0)
};
document.body.onclick = function C() {
input.value += ‘ body’
};
上面代码在点击按钮后,先触发回调函数 A,然后触发函数 C。函数 A 中,setTimeout 将函数 B 推迟到下一轮事件循环执行,这样就起到了,先触发父元素的回调函数 C 的目的了。
6.2.2 另一个应用是,用户自定义的回调函数,通常在浏览器的默认动作之前触发。
比如,用户在输入框输入文本,keypress 事件会在浏览器接收文本之前触发。因此,下面的回调函数是达不到目的的。
// HTML 代码如下
// <input type=”text” id=”input-box”>
document.getElementById(‘input-box’).onkeypress = function (event) {
this.value = this.value.toUpperCase();
}
上面代码想在用户每次输入文本后,立即将字符转为大写。但是实际上,它只能将本次输入前的字符转为大写,因为浏览器此时还没接收到新的文本,所以 this.value 取不到最新输入的那个字符。只有用 setTimeout 改写,上面的代码才能发挥作用。
document.getElementById(‘input-box’).onkeypress = function() {
var self = this;
setTimeout(function() {
self.value = self.value.toUpperCase();
}, 0);
}
上面代码将代码放入 setTimeout 之中,就能使得它在浏览器接收到文本之后触发
由于 setTimeout(f, 0)实际上意味着,将任务放到浏览器最早可得的空闲时段执行,所以那些计算量大、耗时长的任务,常常会被放到几个小部分,分别放到 setTimeout(f, 0)里面执行。
var div = document.getElementsByTagName(‘div’)[0];
// 写法一
for (var i = 0xA00000; i < 0xFFFFFF; i++) {
div.style.backgroundColor = ‘#’ + i.toString(16);
}
// 写法二
var timer;
var i=0x100000;
function func() {
timer = setTimeout(func, 0);
div.style.backgroundColor = ‘#’ + i.toString(16);
if (i++ == 0xFFFFFF) clearTimeout(timer);
}
timer = setTimeout(func, 0);
上面代码有两种写法,都是改变一个网页元素的背景色。写法一会造成浏览器“堵塞”,因为 JavaScript 执行速度远高于 DOM,会造成大量 DOM 操作“堆积”,而写法二就不会,这就是 setTimeout(f, 0)的好处。
6.2.3 另一个使用这种技巧的例子是代码高亮的处理
如果代码块很大,一次性处理,可能会对性能造成很大的压力,那么将其分成一个个小块,一次处理一块,比如写成 setTimeout(highlightNext, 50)的样子,性能压力就会减轻