理解JS异步机制记录(未整理版)

事件处理过程

  • 浏览器检查事件队列头
  • 如果在队列头并没有事件则继续检查后面
  • 如果队列头有事件则去除并执行

为何要有事件队列?

因为浏览器处理事件是单线程的,这里历史原因在于单线程可以保证页面在同一时刻只被同一事件修改
又由于请求和数据的输入输出较慢,所以cpu空闲,所以提供这种事件循环机制使其进入任务队列,让排在后面的任务先执行,等数据到来后,进程空闲时按队列顺序处理之前的请求任务。

同步任务和异步任务与事件队列的关系

同步任务:主线程执行,任务按顺序一个一个执行
异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

(1)所有同步任务都在主线程上执行,形成一个执行栈。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。(回调函数、事件触发、计时器触发等)

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

主线程任务结束后执行任务队列里任务,迭代执行。
摘自阮一峰老师博客

定时器又是什么?与其有什么关系?

  • setTimeout():setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。对应的setTimeout取消定时器
  • setInterval():setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。对应的setInterval函数取消定时器
  • 这两个定时器均会返回一个值,将这个值传入对应取消函数中即可
var h = setTimeout(Fn,1000);
var h2 = setInterval(Fn,1000);

clearTimeout(h);
clearInterval(h2);

一些疑点

  1. 如何使用计时器将导致应用程序缓慢甚至不响应的长时间任务,分解为不会阻塞浏览器的小任务。
    使用setTimeout(Fn,0)将大任务切小为多个小任务执行, HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次 截取自阮一峰博客,长时间的任务会阻塞渲染,由此引发页面的假死,若将大任务切分后,可替换为多次页面更新。
function chunk (array,process,context){
  setTimeout(function() {
    var item = array.shift()
    process.call(context,item)
    
    if(array.length > 0){
      setTimeout(arguments.callee,100)
    }
  },100)
}
//摘自JavaScript高级程序设计
  1. 定时器也会在任务队列中,那么何时执行呢?是在设置的时间到了就执行吗?
    它所设置的时间指的就是在设定的时间到了后加入任务队列等待执行,如果此时队列中没有任务则会被执行,如果有则需要等待。这也是间隔计时器不会在预期执行的原因。因为你只能控制何时加入而不是何时执行。

  2. 那为何定时器的执行时间无法保证?
    无法保证是因为进入队列后,在执行它前的任务所需的事件是无法预期的。

  3. 如果setInterval 定时器每 3ms 执行一次,而事件处理程序需要运行16 ms,那么定时器的回调函数将被添加到微任务队列中多少次?
    一次,并不会重复添加,也就是不会创建两个相同的间隔计时器

这里有个问题,如果在事件运行到第15ms时增加的定时器会在18ms时执行,那么并没有起到事件结束后延时3ms运行的 效果,那么此时可以参考下面这个问题设计出信的解决方案。即对于setTimeout()的重复调用。
setTimeout(function() {
  setTimeout(arguments.callee , interval)
} , interval)

// arguments.callee 获取当前执行的函数的引用
拓展:arguments对象可以获取和设置函数的参数,通过arguments[0]这种形式获取、设置参数值,而 arguments.callee 则是指向当前执行的函数。arguments.length 指向传递给当前函数的参数数量。
  1. setTimeout() 和 setInterval()差异?
    重复调用setTimeout() 时 它的执行思路是这样的:假设setTimeout(Fn,Delay)这里的回调函数Fn执行时间是12ms,则Delay为10ms后任务加入队列,在无阻塞情况下执行,执行时间12ms,执行结束后,再等待10ms后任务再次加入队列,然后执行时间依然为12ms,重复以上。而setInterval(Fn,Delay) 则fn12ms执行时间,Delay为10ms,它会在第一个10ms后加入任务队列无阻塞情况下执行,当总时间为20ms时任务再次加入队列,而不是等待函数执行完毕后加入。

  2. setTimeout()如何设置防止资源未加载成功时多次发送请求?
    原理是第一次调用函数创建一个定时器,在指定时间间隔后执行代码,第二次调用时会清除上一次创建的定时器,并设置一个新的(即在设置这个新的计时器后再 在指定间隔后执行)。

  var timer
  function fn(){
      if(timer){
          clearTimeout(timer)
      }
        timer = setTimeout(function(){
             console.log('OK!')
        }, 1000)
  }

  fn()
  fn()
  fn()

在第一次执行fn后,1000ms内执行了第二次fn,此时由于代码需要在1000ms后加入队列执行,所以没有输出的情况下被清除了(clearTimeout(timer)),也就是不会重复执行此函数,如果这里面是ajax请求也同样不会多次发送请求。


Event Loop

image

在这里stack里的代码总是会先执行,在结束后执行任务队列。

posted on 2018-08-03 00:47  2481  阅读(149)  评论(0编辑  收藏  举报

导航