你应该知道的setTimeout秘密
setTimeout
是我们经常会用到的,它用于在指定的毫秒数后调用函数或计算表达式。setTimeout(code, millisec, args);
eval()
方法来执行code。setTimeout
,而且理解其是如何执行的。var start = new Date();
var end = 0;
setTimeout(function() {
console.log(new Date() - start);
}, 500);
while (new Date() - start <= 1000) {}
JavaScript引擎线程
,GUI渲染线程
,浏览器事件触发线程
。
JavaScript引擎
是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。GUI渲染线程
负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。事件触发线程
,当一个事件被触发时,该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系,所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。
var start = new Date();
var end = 0;
setTimeout(function() {
console.log(new Date() - start);
}, 500);
while (new Date() - start <= 1000) {}
setTimeout
的延时时间是500毫秒,可是由于while
循环的存在,只有当间隔时间大于1000毫秒时,才会跳出while
循环,也就是说,在1000毫秒之前,while
循环都在占据着JavaScript线程。也就是说,只有等待跳出while
后,线程才会空闲下来,才会去执行之前定义的setTimeout
。setTimeout
只能保证在指定的时间后将任务(需要执行的函数)插入任务队列中等候,但是不保证这个任务在什么时候执行。一旦执行javascript的线程空闲出来,自行从队列中取出任务然后执行它。setTimeout(function(){
// statement
}, 0);
setTimeout
有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout
的时间间隔,也就是说即使我们把setTimeout
的延迟时间设置为0,被调用的程序也没有马上启动。setTimeout(fn,0)
的时候,实际是实现插队操作,要求浏览器“尽可能快
”的进行回调,但是实际能多快就完全取决于浏览器了。setTimeout(fn, 0)
有什么用处呢?其实用处就在于我们可以改变任务的执行顺序!因为浏览器会在执行完当前任务队列中的任务,再执行setTimeout队列中积累的的任务。document.querySelector('#one input').onkeydown = function() {
document.querySelector('#one span').innerHTML = this.value;
};
document.querySelector('#second input').onkeydown = function() {
setTimeout(function() {
document.querySelector('#second span').innerHTML = document.querySelector('#second input').value; }, 0);
};
keydown
的事件处理程序,然后更新文本框的 value 值,这两个任务也需要按顺序来,事件处理程序执行时,更新 value值(是在keypress
后)的任务则进入队列等待,所以我们在 keydown
的事件处理程序里是无法得到更新后的value的,而利用 setTimeout(fn, 0)
,我们把取 value 的操作放入队列,放在更新 value 值以后,这样便可获取出文本框的值。setTimeout
函数,执行顺序是:onkeydown => onkeypress => onkeyupsetTimeout
函数,执行顺序是:onkeydown => onkeypress => function => onkeyupkeyup
来替代keydown
,不过有一些问题,那就是长按时,keyup
并不会触发。keydown、keypress、keyup
的调用顺序:keydown
keypress
keydown
keypress
...
keyup
也就是说keyup
只会触发一次,所以你无法用keyup
来实时获取值。setImmediate()
来替代setTimeout(fn,0)
:if (!window.setImmediate) {
window.setImmediate = function(func, args){
return window.setTimeout(func, 0, args);
};
window.clearImmediate = window.clearTimeout;
}
func
,表示将要执行的回调函数,它并不需要时间参数。注意:目前只有IE10支持此方法,当然,在Nodejs中也可以调用此方法。
3、setTimeout的一些秘密
3.1 setTimeout中回调函数的this
由于setTimeout()
方法是浏览器 window 对象提供的,因此第一个参数函数中的this
其实是指向window
对象,这跟变量的作用域有关。
看个例子:
var a = 1;
var obj = {
a: 2,
test: function() {
setTimeout(function(){
console.log(this.a);
}, 0);
}
};
obj.test(); // 1
不过我们可以通过使用bind()
方法来改变setTimeout
回调函数里的this
var a = 1;
var obj = {
a: 2,
test: function() {
setTimeout(function(){
console.log(this.a);
}.bind(this), 0);
}
};
obj.test(); // 2
3.2 setTimeout不止两个参数
我们都知道,setTimeout
的第一个参数是要执行的回调函数,第二个参数是延迟时间(如果省略,会由浏览器自动设置。在IE,FireFox中,第一次配可能给个很大的数字,100ms上下,往后会缩小到最小时间间隔,Safari,chrome,opera则多为10ms上下。)
其实,setTimeout
可以传入第三个参数、第四个参数....,它们表示神马呢?其实是用来表示第一个参数(回调函数)传入的参数。
setTimeout(function(a, b){
console.log(a); // 3
console.log(b); // 4
},0, 3, 4);
如果你有疑问或建议,欢迎在下面的评论区评论!