setTimeout
for(var i = 0; i < 6; i++){
setTimeout(function(){
console.log(i);
},1000);
}
上述代码会连续输出6个6,因为setTimeout
是一个异步操作,而等到执行setTimeout
时,for循环已经执行完毕,这时的i
已经等于6,所以输出6次的6。
解决上述情况的办法,使用闭包。
for(var i = 1; i < 7; i++){
(function(j){
setTimeout(function(){
console.log(j) // 打印 1 2 3 4 5 6
},j*1000)
})(i)
}
通过闭包,将i
的变量驻存在内存中,当输出j
时,引用的是外部函数A
的变量值i
,i
的值是根据循环出来的,执行setTimeout时已经确定里面的输出了。
setTimeout原理
javaScript
是单线程执行的。在任何时间点,有且只有一个线程在运行javaScript
程序,无法同一时候运行多段代码。
浏览器下的javaScript
浏览器的内核是多线程的,它们在内核控制相互配合以保持同步,一个浏览器至少实现三个常驻线程:javaScript引擎线程
,GUI渲染线程
,浏览器事件触发线程
。
javaScript引擎线程:是基于事件驱动单线程执行的,javaScript
引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个javaScript
线程在运行javaScript
程序。
GUI渲染线程:负责浏览器渲染页面,当界面需要重绘或由于某种操作引发回流时,该线程就会执行。但需要注意,GUI渲染线程与javaScript
引擎是互斥的,当javaScript
引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到javaScript
引擎空闲时立即被执行。
事件触发线程:当一个事件被触发时,该线程会把事件添加到待处理队列的队尾,等待javaScript
引擎的处理。这些事件可来自javaScript
引擎当前执行的代码块如setTimeout
、也可来自浏览器内核的其他线程如鼠标点击、ajax
异步请求,但由于javaScript
单线程关系,所有这些事件都得排队等待javaScript
引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。
var time = new Date();
setTimeout(function(){
console.log(new Date() - time);
},500);
while(new Date() - time <= 1000){}
虽然setTimeout
的延迟时间是500ms,但是因为while
的循环的存在,只有当事件间隔大于1000ms时,才会跳出while
循环,即在1000ms之前,while
循环都在占据javaScript
线程。也就是说,只有等待跳出while
后,线程才会空闲下来,才会去执行之前定义的setTimeout
。
最后,总结,setTimeout只能保证在指定的时间将任务(需要执行的函数)插入到任务队列中等候,但是不保证这个任务在什么时候执行,一旦执行javaScript的线程空闲出来,自行从队列中取出任务然后执行它。
因为javaScript
线程并没有因为什么耗时操作而阻塞,所以可以很快取出排队队列中的任务然后执行它,也是这种队列机制,给我们制造一个异步执行的假象。
在setTimeout中使用“0”
setTimeout(function(){
// 代码
},0);
这段代码表示立即执行。
本意是立刻执行调用函数,但事实上,上面的代码并不是立即执行的,这是因为setTimeout
有一个最小执行时间,当指定的事件小于该时间,浏览器就会用最小允许时间作为setTimeout
的时间间隔,也就是说即使我们把setTimeout
的延迟时间设置为0,被调用的程序也没有马上启动。
当我们写为setTimeout(fn,0)
的时候,实际上是实现插队操作,要求浏览器“尽可能快”的进行回调,但是实际能多快就完全取决于浏览器。
setTimeout(fn,0)
的用处,在于我们可以改变任务的执行顺序,因为浏览器会在执行完当前任务队列中的任务,再执行setTimeout
队列中累积的任务。
通过设置任务在延迟到0s后执行,就能改变任务执行的先后顺序,延迟该任务发生,使之异步执行。
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)
}
未使用setTimeout
函数只会获取到输入前的内容。而使用setTimeout
函数的则会获取到输入的内容。
在按下按键的时候,javaScript
引擎需要执行keydown
的事件处理程序,然后更新文本框的value
值,这两个任务也需要按顺序来,事件处理程序执行时,更新value
值的任务则进入队列等待,所以我们在keydown的事件处理程序时无法得到更新后的value
的,而利用setTimeout(fn,0)
,我们把取value
的操作放入队列,放在更新value
值之后,这样便可获取出文本框的值。
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
setTimeout的参数
第一个参数是要执行的回调函数,第二个参数是延迟时间,setTimeout
可以传入第三个参数、第四个参数...,这些都表示第一个参数(回调函数)传入的参数。
setTimeout(function(a,b){
console.log(a);
console.log(b);
},0,3,4);