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的变量值ii的值是根据循环出来的,执行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);
posted @ 2020-01-17 14:28  zhongfang99  阅读(251)  评论(0编辑  收藏  举报