异步操做(二)定时器

JavaScript 提供定时执行代码的功能,叫作定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成。它们向任务队列添加定时任务ajax

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;app

var obj = {
x: 2,
y: function () {函数

console.log(this.x);

}
};性能

setTimeout(obj.y, 1000) // 1
上面代码输出的是1,而不是2。由于当obj.y在1000毫秒后运行时,this所指向的已经不是obj了,而是全局环境。动画

为了防止出现这个问题,一种解决方法是将obj.y放入一个函数。this

var x = 1;code

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)的样子,性能压力就会减轻

相关文章
相关标签/搜索