dashengzi66 / note

学习笔记
0 stars 0 forks source link

EventLoop #12

Open dashengzi66 opened 3 years ago

dashengzi66 commented 3 years ago

定义:Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理

宏任务和微任务都属于异步任务,一个宏任务执行结束后会查看微任务队列是否为空而去决定执行微任务还是执行下一个宏任务。

在图中: 事件循环将首先检查宏任务队列,如果宏任务等待,则立即开始执行宏任务,直到该任务运行完成,(或者任务队列为空),事件循环将移动去处理微任务队列,如果有任务在该队列中等待,则事件循环将依次开始执行,完成一个后执行余下的微任务,直到队列中所有微任务执行完毕。 注意处理宏任务和微任务队列之间的区别: 单次循环迭代中,最多处理一个宏任务,(其余的再队列中等待),而队列中的所有微任务都会被处理。 当微任务队列处理完成并清空时,事件循环会检查是否需要进行更新UI渲染。如果需要则会重新渲染UI视图。 至此,当前事件循环结束,之后将会回到最初的第一个环节,再次检查宏任务队列,并开启新一轮的事件循环。

事件循环存在许多细节需要明确,如下:

  1. 宏任务队列和微任务队列都是独立于事件循环的,这也就意味着任务队列的添加行为也发生在事件循环之外, (为什么任务队列的添加行为要独立于事件循环之外?主要是因为防止阻塞事件循环导致浏览器响应用户交互缓慢) 因此检查和添加任务的行为,是独立于事件循环完成的。
  2. 因为JS基于单线程执行模型,所以宏任务和微任务都是逐个执行的,当一个任务开始执行后,在完成之前,中间不会被任何任务中断,除非浏览器决定终止执行该任务,比如某个任务执行时间过长或者内存占用过大。
  3. 所有微任务会在下一次渲染之前执行完成, 因为他们要在渲染UI之前完成更新应用程序的状态。
  4. 浏览器通常会尝试每秒渲染60次页面,已到达每秒60帧的速度,这意味着浏览器会尝试在16ms内渲染一帧,在理想情况下,单个任务和该任务附属的所有微任务都应在16ms内完成。
  5. 现在然我们思考一下,在浏览器完成页面渲染,进入下一轮事件循环迭代后,可能发生的三种情况。
  6. 在另一个16ms结束前,事件循环执行到”是否需要进行渲染“的决策环节,因为更新UI是一个复杂的操作,所以如果没有显式的指定需要页面渲染,浏览器可能不会选择在当前的循环中执行UI渲染操作。
  7. 在最后一次渲染完成大约16ms,事件循环执行到”是否需要进行渲染“的决策环节,在这种情况下,浏览器会进行UI更新,以便用户能够感受到顺畅的应用体验。
  8. 执行下一个任务(和相关的所有微任务)耗时超过16ms,在这种情况下,浏览器将无法以目测帧率重新渲染页面,且UI无法更新。
dashengzi66 commented 3 years ago

练习题1

new Promise(resolve=>{
  console.log('promise1');
  resolve();
}).then(()=>{
  console.log('then11');
  new Promise(resolve=>{
    console.log('promise2');
    resolve();
  }).then(()=>{
    console.log('then21');
  }).then(()=>{
    console.log('then22');
  })
}).then(()=>{
  console.log('then12');
})
//执行结果:promise1->then11->promise2->then21->then12->then22
dashengzi66 commented 3 years ago

练习题 2 文章来源:https://juejin.cn/post/6994609420495765540

<script>
  // 宏任务 与 微任务 示例
  setTimeout(() => {
    console.log('set-1')
  })
  setTimeout(() => {
    console.log('set-2')
  }, 100)
  setTimeout(() => {
    console.log('set-3')
  }, 10)

  const pro = new Promise((res, rej) => {
    console.log('1');
    res('2')
    console.log('3')
  })
  pro.then(res => {
    console.log("res", res)
    return '4'
  }).then(res => {
    console.log("res", res)
  })
  setTimeout(() => {
    pro.then((res) => {
      console.log("res", res)
      console.log('5')
    })
    console.log('7')
  }, 1000)
  setTimeout(async () => {
    console.log('9')
    const num = await pro.then((res) => {
      console.log("res", res)
      return '8'
    });
    console.log("num", num)
  }, 0)
  console.log('6')
</script>
  1. script 片段以宏任务的添加进任务队列,开始执行。
  2. 在这个script片段中的代码从上至下一次执行,首先遇到了三个setTimeOut,有浏览器控制在事件循环之外等待延时时间到来,之后添加进入宏任务队列。
  3. 接下来执行new Promise(),因为 Promise是立即执行函数,所以会立即执行 console.log('1') res('2') console.log('3'),打印 1,3。
  4. 代码执行到了pro.then().then() 因为这些事微任务,所以全部添加入微任务队列。留待本次宏任务执行完之后执行。
  5. 接下了两个setTimeOut()依然会由浏览器控制,在事件循环之外等待延时时间到来,之后添加入宏任务队列。这两个宏任务会和上面的三个宏任务一起进行等待添加入宏任务队列,它们会根据执行的顺序和等待的延时时间,顺序的添加入宏任务队列。
  6. 执行 console.log('6') 打印 6, 本次宏任务执行完毕,接下来查看微任务队列,执行所有的微任务。
  7. 因为目前的微任务队列中有pro.then().then(),执行所有的微任务,所以会打印 res 2,res 4。 接下来浏览器会根据情况是否进行UI渲染。接着查看宏任务队列,执行下一个宏任务。
  8. 开始执行 setTimeout(() => { console.log('set-1') }); 这个宏任务,回调函数被调用执行,会打印出 set-1。在这里为什么会是它先进入宏任务队列呢? 两个原因,一个是因为它没有第二个参数,浏览器会默认至少16ms之后会执行它,另一个就是代码从上到下它先执行。 之后会查看是否存在微任务,若无,执行下一个宏任务。
  9. 开始执行 setTimeout(async() => { },0); 这个宏任务,先打印 9 然后 await等待变为同步,打印res 2 之后会往下执行打印 num 8。 那么为什么会是这个宏任务在宏任务队列的第二个呢? 两个原因:一个是因为它的第二个参数为0,其实浏览器依然会至少16ms之后会执行它,另一个是,上一个宏任务比它先执行,所以它是第二个。 之后会查看是否存在微任务,若无,执行下一个宏任务。
  10. 开始执行 setTimeout(() => { console.log('set-3') }, 10); 这个宏任务; 打印 set-3。这个为什么是第三个执行你自己考虑考虑。之后会查看是否存在微任务,若无执行下一个宏任务。
  11. 开始执行 setTimeout(() => { console.log('set-2') }, 100); 这个宏任务,打印 set-2。之后会查看是否存在微任务,若无,执行下一个宏任务。
  12. 开始执行 setTimeout(() => { }, 1000) 这个宏任务,遇到pro.then(),添加进入微任务队列,之后向后执行打印 7 ,本次宏任务执行完毕,查看微任务队列,执行所有的微任务。 打印 res 2,5。之后会查看是否存在微任务,若无,执行下一个宏任务。
  13. 若无宏任务,等待用户交互。
dashengzi66 commented 3 years ago

练习题3

console.log('我是script开始!')
setTimeout(() => {
    console.log('我是setTimeout!')
}, 0)
new Promise((resolve)=>{
    console.log('我是Promise!')
    resolve()
}).then(()=>{
    console.log('我是Promise.then!')
})
console.log('我是script结束!')

//执行结果: '我是script开始!' --> '我是Promise!' --> '我是script结束!' --> '我是Promise.then!' --> '我是setTimeout!'

dashengzi66 commented 3 years ago

image

dashengzi66 commented 3 years ago

练习题4

    console.log('script start');
    async function async1() {
      await async2();
      console.log('async1 end');
    }
    async function async2() {
      console.log('async2 end');
    }
    async1();
    setTimeout(function () {
      console.log('setTimeout');
    }, 0);
    new Promise(resolve => {
        console.log('Promise');
        resolve();
      })
      .then(function () {
        console.log('promise1');
      })
      .then(function () {
        console.log('promise2');
      });
    console.log('script end');

//执行结果:script start --> async2 end --> Promise --> script end -->async1 end --> promise1 --> promise2 --> setTimeout

dashengzi66 commented 3 years ago
03d9b2624d3bc992c7344331f965b02
plzhoupl commented 3 years ago

https://mp.weixin.qq.com/s/a_vfNw0rI2bZHG9xY_7z1Q