事件轮询机制 Event Loop、浏览器更新渲染时机、setTimeout VS setInterval

目录

1. 事件轮询机制(Event Loop)是什么

1.1 宏任务、微任务

1.2 Event Loop 循环过程

1.3 经典题目分析

1.3.1 第一轮事件循环

1.3.2 第二、三次事件循环

1.3.3 参考文章

2. async、await 在事件轮询中的执行时机

3. 浏览器更新渲染时机、Vue nextTick

3.1 Event Loop 与 浏览器更新渲染时机

3.2 Vue 为什么要优先使用微任务实现 nextTick

4. Node.js 中的 process.nextTick

4.1 什么是 process.nextTick

4.2 浏览器事件循环 VS  Node 事件循环

5. setTimeout VS setInterval

5.1 setTimeout / setInterval 的执行时间为啥无法确定呢?

5.2 setTimeout / setInterval 概念容易踩得坑

5.2.1 setTimeout 举个栗子~

5.2.2 setInterval 举个栗子~

5.2.3 setTimeout 模拟实现 setInterval

5.2.4 setInterval 模拟实现 setTimeout

5.3 requestAnimationFrame 浏览器提供的动画 API

5.3.1 setTimeout / setInterval 动画卡顿

5.3.2 什么是 requestAnimationFrame

5.3.3 setTimeout VS setInterval VS requestAnimationFrame

5.3.4 参考文章


HTML5 中的 WebWoker,号称 JavaScript 多线程版

其实本质上还是用 JavaScript 单线程模拟出来的,所以搞明白 Event Loop 很重要

 

1. 事件轮询机制(Event Loop)是什么

1.1 宏任务、微任务

任务可以分成两种:

  • 宏任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行下一个任务
  • 微任务:不进入主线程、而进入"微任务列表" 的任务

宏任务(Macrotasks)包括:

  • script 全部代码(注意:同步代码也属于宏任务)
  • setTimeout
  • setInterval
  • setImmediate

微任务(Microtasks)包括:

  • Promise
  • MutationObserver

不同类型的任务,会进入对应的 Event Queue,比如 setTimeout 和 setInterval 会进入相同的 Event Queue

1.2 Event Loop 循环过程

JavaScript 是单线程的,同一时间只能做一件事。所有任务都需要排队,前一个任务结束,才会执行后一个任务(JavaScript 是一门单线程语言,Event Loop 是 JavaScript 的执行机制)

  • 宏任务是一个个执行;
  • 如果执行过程中,遇到微任务,就把他加入微任务队列;
  • 当前宏任务执行完后,会判断 微任务列表 中是否有任务;
  • 如果有,就把该微任务放到主线程中执行;如果没有,就继续执行下一个宏任务,不断循环;

用 Ajax 举个栗子:

  • Ajax 作为微任务,进入微任务列表,注册回调函数 success
  • 执行同步代码,console.log('代码执行结束')
  • 等待 Ajax 请求完成,主线程从 微任务列表 读取回调函数 success 并执行
let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');

1.3 经典题目分析

Promise.resolve()
  .then(function() {
    console.log("promise0"); // 2 执行微任务1 Promise1
  })
  .then(function() {
    console.log("promise5"); // 4 执行微任务3 Promise3
  });

setTimeout(() => {
  console.log("timer1"); // 5 执行宏任务 setTimeout1
  Promise.resolve().then(function() {
    console.log("promise2"); // 6 执行宏任务中的微任务
  });
  Promise.resolve().then(function() {
    console.log("promise4"); // 7 执行宏任务中的微任务
  });
}, 0);

setTimeout(() => {
  console.log("timer2"); // 8 执行宏任务 setTimeout2
  Promise.resolve().then(function() {
    console.log("promise3"); // 9 执行宏任务中的微任务
  });
}, 0);

Promise.resolve().then(function() {
  console.log("promise1"); // 3 执行微任务2 Promise2
});

console.log("start"); // 1 执行同步代码

打印结果: start ------ promise0 ------ promise1 ------ promise5 ------ timer1 ------ promise2 ------ promise4 ------ timer2 ------ promise3

 

1.3.1 第一轮事件循环

  • 整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 start
  • 遇到 Promise,其回调函数 then 被分发到 微任务队列 中。记为 Promise1
  • 遇到 Promise,其回调函数 then 被分发到 微任务队列 中。记为 Promise2
  • 遇到 setTimeout,分发到 宏任务队列。记为 setTimeout1
  • 遇到 setTimeout,分发到 宏任务队列。记为 setTimeout2
  • 遇到 Promise,其回调函数 then 被分发到 微任务队列 中。记为 Promise3

宏任务队列:

  • setTimeout1
  • setTimeout2

微任务队列:

  • Promise1
  • Promise2
  • Promise3

执行同步代码,输出 start

执行微任务队列,输出 promise0 ------ promise1 ------ promise5

第 2、3、4 步的输出可能会有疑问

这里举个栗子~

new Promise(function(resolve) {
    // 这里作为同步代码,立即执行
    console.log('promise');
}).then(function() {
    // then 中的代码,会被放入微任务队列
    console.log('then');
})

可以看出:

  • new Promise 中的代码,是立即执行的
  • 而回调函数 then,会被放到微任务队列中

1.3.2 第二、三次事件循环

第二次事件循环:

  • 执行第二个宏任务 setTimeout1
  • 先执行同步代码,输出 timer1
  • 再将微任务放到微任务列表中,由于主线程没有代码可执行,所以将微任务放到主任务线程中,输出 promise2 ------ promise4

第三次事件循环:

  • 执行第三个宏任务 setTimeout2
  • 先执行同步代码,输出 timer2
  • 再将微任务放到微任务列表中,由于主线程没有代码可执行,所以将微任务放到主任务线程中,输出 promise3

1.3.3 参考文章

这篇文章的示例讲解比较清楚,值得学习

这一次,彻底弄懂 JavaScript 执行机制 - 掘金本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序。因为javascript是一门单线程…https://juejin.cn/post/6844903512845860872

2. async、await 在事件轮询中的执行时机

async 隐式返回 Promise,会产生一个微任务

await 后面的代码,在执行微任务时执行

举个栗子 ~~ 看下面的代码

console.log("script start"); // 1 同步代码

async function async1() {
  await async2(); // 2 同步代码
  console.log("async1 end"); // 5 这里的执行时机:在执行微任务时执行
}

async function async2() {
  console.log("async2 end");
}

async1();

setTimeout(function() {
  console.log("setTimeout"); // 8 第二个宏任务
}, 0);

new Promise(resolve => {
  console.log("Promise"); // 3 同步代码
  resolve();
})
  .then(function() {
    console.log("promise1"); // 6 微任务
  })
  .then(function() {
    console.log("promise2"); // 7 微任务
  }); 

console.log("script end"); // 4 同步代码

打印结果: script start ------ async2 end ------ Promise ------ script end ------ async1 end ------ promise1 ------ promise2 ------ setTimeout

第一次事件循环:

  • 整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出  script start
  • 执行 async1,里面的 await async2 是个同步代码,所以打印了 async2 end;同时,async1 还产生一个微任务(就是 await 后面的代码),放进微任务列表,暂时不执行
  • 继续执行主任务,走到 promise,走里面的同步代码,打印了 Promise,里面的 then 放进微任务列表,暂时不执行
  • 继续执行主任务,执行同步代码 console.log...,打印了 script end
  • 此时微任务列表里,首个微任务是 await 后面的代码,打印了 async1 end;剩下两个微任务是 promise 里的,打印了 promise1、promise2
  • 第一个宏任务执行完毕

第二次事件循环:

  • 执行第二个宏任务 setTimeout,打印了 setTimeout

3. 浏览器更新渲染时机、Vue nextTick

3.1 Event Loop 与 浏览器更新渲染时机

浏览器更新渲染,会在 Event Loop 中的 宏任务、微任务 完成后进行

先宏任务,再微任务,然后再渲染更新

宏任务队列中,如果有大量任务等待执行时,应该将 修改 DOM 作为微任务,这样就能在 本次事件轮询中 更新 DOM(Vue nextTick 就是这么实现的),更快的将 页面变化 呈现给用户

从event loop规范探究javaScript异步及浏览器更新渲染时机 · Issue #5 · aooy/blog · GitHub作者:杨敬卓 转载请注明出处 异步的思考 event loops隐藏得比较深,很多人对它很陌生。但提起异步,相信每个人都知道。异步背后的“靠山”就是event loops。这里的异步准确的说应该叫浏览器的event loops或者说是javaScript运行环境的event loops,因为ECMAScript中没有event loops,event loops是在HTML Standard定义的。 event loops规范中定义了浏览器何时进行渲染更新,了解它有助于...https://github.com/aooy/blog/issues/5

3.2 Vue 为什么要优先使用微任务实现 nextTick

Vue3 nextTick 源码地址:core\packages\runtime-core\src\scheduler.ts

Vue nextTick 的码的优先级判断:Promise > MutationObserver > setImmediate > setTimeout

答:根据 Event Loop 与浏览器更新渲染时机可得:

  • 优先使用 Promise 等微任务,本次事件轮询就能获得更新的 DOM
  • 如果使用宏任务,要到下一次事件轮询中,才能获得更新的 DOM

学习Vue3 第三十五章(Evnet Loop 和 nextTick)_小满zs的博客-CSDN博客在我们学习nextTick 之前需要先了解Event Loop 时间循环机制在我们学js 的时候都知道js 是单线程的如果是多线程的话会引发一个问题在同一时间同时操作DOM 一个增加一个删除JS就不知道到底要干嘛了,所以这个语言是单线程的但是随着HTML5到来js也支持了多线程webWorker 但是也是不允许操作DOM单线程就意味着所有的任务都需要排队,后面的任务需要等前面的任务执行完才能执行,如果前面的任务耗时过长,后面的任务就需要一直等,一些从用户角度上不需要等待的任务就会一直等待,这个从体验角度上来https://xiaoman.blog.csdn.net/article/details/125237755https://github.com/qingzhou729/study/issues/15icon-default.png?t=M85Bhttps://github.com/qingzhou729/study/issues/15

4. Node.js 中的 process.nextTick

4.1 什么是 process.nextTick

process.nextTick 相当于 Node.js 中的 setTimeout,是 Node.js 定义的一种机制,它有自己的 nextTickQueue

process.nextTick 不能完全当作 JavaScript 中的微任务,process.nextTick 执行顺序早于微任务

举个栗子 ~~ 看下面的代码:

console.log("start"); // 1 同步代码

setTimeout(() => {
  console.log("setTimeout"); // 6 第二个宏任务
}, 0);

Promise.resolve().then(() => {
  // 4 第一个微任务,早于 process.nextTick 内部的 promise 放入微任务列表
  console.log("promise");
});

process.nextTick(() => {
  console.log("nextTick"); // 3 Node.js process.nextTick 早于微任务执行
  Promise.resolve().then(() => {
    console.log("promise1"); // 5 第二个微任务
  });
});

console.log("end"); // 2 同步代码

执行结果:start ------ end ------ nextTick ------ promise ------ promise1 ------ setTimeout

4.2 浏览器事件循环 VS  Node 事件循环

浏览器与Node的事件循环(Event Loop)有何区别? - 知乎摘要: 浏览器和Node中Event Loop其实是不相同的。前言本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的。 一、线程与进程1. 概念我们经常说 JS 是单线程执行的,指…https://zhuanlan.zhihu.com/p/54882306

5. setTimeout VS setInterval

  • setTimeout 固定时长后执行
  • setInterval 间隔固定时间后重复执行

5.1 setTimeout / setInterval 的执行时间为啥无法确定呢?

举个栗子~ 

定时器内部代码,需要进行大量计算,或者 DOM 操作,代码执行时间 超过了 定时器设置的时间

再举个栗子~

setTimeout 早早的把 task() 放进等待队列了,但是由于 JavaScript 是单线程的,只能等 sleep() 执行完毕(时间远超 3s),才能执行 task()

setTimeout(() => {
    // 控制台执行 task() 需要的时间,远远超过 3 秒
    task()
}, 3000)

sleep(10000000)

5.2 setTimeout / setInterval 概念容易踩得坑

5.2.1 setTimeout 举个栗子~

setTimeout(fn, 0),可以立即执行吗?答案是不行。

  • setTimeout 指的是:主线程的任务都执行完了,再等 x 秒,才能执行内部代码;
  • 即使主线程为空,也不能立即执行,因为 JavaScript 规定 setTimeout、setInterval 最短时长为 4ms;

5.2.2 setInterval 举个栗子~

对于 setInterval(fn, ms) 来说,他是每过 ms 秒就一定会执行一次 fn 吗?答案是不是。

  • 是每过 ms 秒,会创建一个 fn 进入任务队列;
  • 一旦 setInterval 的回调函数 fn 实际执行时间,超过了延迟时间 ms,那么就完全看不出来有时间间隔了;

5.2.3 setTimeout 模拟实现 setInterval

// 使用闭包实现
function mySetInterval(fn, t) {
  let timer = null;
  function interval() {
    fn();
    timer = setTimeout(interval, t);
  }
  interval();
  return {
    // cancel用来清除定时器
    cancel() {
      clearTimeout(timer);
    }
  };
}

5.2.4 setInterval 模拟实现 setTimeout

function mySetTimeout(fn, time) {
  let timer = setInterval(() => {
    clearInterval(timer);
    fn();
  }, time);
}

// 使用
mySetTimeout(() => {
  console.log(1);
}, 2000);

 

5.3 requestAnimationFrame 浏览器提供的动画 API

5.3.1 setTimeout / setInterval 动画卡顿

setTimeout / setInterval 通过设置一个间隔时间,不断改变 css 实现动画效果,在不同设备上可能会出现卡顿、抖动等现象,这是因为:

  • 不同设备的屏幕,刷新频率可能不同
  • setTimeout / setInterval 只能设置固定的时间间隔,这个时间 与 屏幕刷新间隔可能不同

5.3.2 什么是 requestAnimationFrame

HTML5 新增 API,类似于 setTimeout,window.requestAnimationFrame

requestAnimationFrame 告诉浏览器,在下次重绘之前,执行传入的回调函数(通常是操作 DOM,更新动画的函数),也就是说:刷新频率与显示器的刷新频率保持一致

requestAnimationFrame 是浏览器专门为 动画(DOM 动画、Canvas 动画、 SVG 动画、WebGL 动画) 提供的API,使用该 API 可避免使用setTimeout / setInterval 造成的动画卡顿

5.3.3 setTimeout VS setInterval VS requestAnimationFrame

引擎层面的区别:

  • setTimeout / setInterval 属于 JavaScript 引擎,存在事件轮询
  • requestAnimationFrame 属于 GUI 引擎

JavaScript 引擎与 GUI 引擎是互斥的,也就是说:

  • GUI 引擎在渲染时,会阻塞 JavaScript 引擎的计算;
  • 如果在 GUI 渲染的时候,JavaScript 又同时改变了DOM,就会造成页面渲染不同步;

性能层面的区别:

  • 当页面被隐藏或最小化时,定时器 setTimeout 仍会在后台运行,执行动画任务
  • 当页面被隐藏或最小化时,屏幕刷新任务会被系统暂停,requestAnimationFrame 也会停止

5.3.4 参考文章

setTimeout/setInterval与requestAnimationFrame的区别?_尹成诺的博客-CSDN博客_settimeout setinter requestanmiationfarme提到 setTimeout/setInterval 以及 requestAnimationFrame,大家的第一反应是动画相关的两个 API。什么是web 动画我们来谈谈什么是动画。动画其实是一种假象,是一种不连续的运动以帧的形式呈现给我 们的东西。在二十世纪,通常人们观看的电影其实就是通过胶片记录和投影的。它是以每 秒至少 24 帧的速度形成的视觉上的运动起来的假象。NTSC 广播的标准...https://blog.csdn.net/weixin_40851188/article/details/89669416

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyrelion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值