js三座大山之异步一单线程,event loope,宏任务&微任务

js三座大山

一:js三座大山之函数1 & js三座大山之函数2-作用域与动态this 即函数式编程。

二:js三座大山之对象,继承,类,原型链即面向对象编程

三:js三座大山之异步一单线程,event loope,宏任务&微任务 即异步编程

单线程:

  • 什么是单线程

    js引擎会从上到下 依次执行所有js代码 在同一个单位时间 js引擎只能执行一段js。当前函数没有被执行完不会执行下一个。

  • 为什么是单线程

    js最初是为了与浏览器交互而设计的,例如响应用户的点击事件、获取或修改DOM等,这些操作需要在同一时间内按照特定的顺序执行,以确保页面的正确渲染。如果JavaScript是多线程的,那么可能会出现同时修改DOM的情况,这可能会导致页面的渲染出现问题。

  • 单线程有什么问题

    1:阻塞:由于JavaScript是单线程的,所以在执行一段耗时的代码时,比如网络请求或者复杂的计算,会阻塞后面的代码执行。这可能导致页面无响应,影响用户体验。如上图,当执行到http的任务可能耗时3s,这段时间内页面会被阻塞无法响应。

    2:效率问题:由于单线程顺序执行,所以先执行http任务耗时3s 然后在执行另一个耗时任务2s,两个耗时任务阻塞了后续一系列其他任务,导致执行效率低。

如何解决单线程带来的问题?

答案是异步编程,JavaScript 引入了事件循环机制。事件循环允许 JavaScript 在等待异步任务完成的同时执行其他任务,从而更好地响应用户操作和处理复杂的应用逻辑。

事件循环event loop

通过异步api 例如定时器,http,promise,i/o等将其当做异步任务,等待异步事件完成 在触发回调将函数推入到任务队列中 等待主线程去执行。避免了异步任务阻塞后续代码执行,影响用户体验。 然后如果任务本身依然比较重那么执行依然会造成UI卡顿,导致页面无法响应,这个问题又该如何解决?先保留问题 后面在看。

宏任务(MacroTask)&微任务(MicroTask)

js中现在有两类任务

一类是天然存在的按顺序依次执行的同步任务

一类是为了解决单线程带来的问题引入的异步任务

无论是同步任务还是异步任务,在执行的过程中都可以产生新的同步任务异步任务。这些任务最终都会被加入到任务队列中等待任务栈去执行。根据触发的方式,在事件循环中的任务又可以分为宏任务&微任务

哪些是宏任务:一般由宿主环境(如浏览器或Node.js)发起的任务

  • 浏览器事件(如 click、mouseover 等)
  • 定时器任务(如 setTimeout 和 setInterval)
  • 页面渲染(如 回流或重绘)
  • 事件回调(如 I/O、点击事件等)
  • 网络请求 (如 XMLHttpRequest 和 fetch 等)

哪些是微任务:一般由JavaScript引擎发起的任务

  • Promise 的回调函数
  • Async/Await 函数
  • MutationObserver 的回调函数
  • process.nextTick(Node.js 环境下)

主要区别

宏任务和微任务主要的区别体现在执行时机上,当事件循环每次执行完一个宏任务时,它会检查是否有微任务队列。如果有,那么在下一个宏任务开始之前,事件循环会一直执行微任务队列中的所有任务。也就是说,微任务的优先级高于宏任务,微任务会在任何新的宏任务开始之前执行,并且直至微任务被清空后才会执行下一个宏任务

注意

宏任务和微任务根据不同的触发方式 在执行时机上有所不同 其他并无区别。并不是说微任务执行性能好于宏任务。很多同学在看过了vue/react源码后 也不知道怎么得出的这个结论 认为微任务比宏任务性能好 这是不对的。无论哪种方式 执行时间过长 都会抢占gui线程的执行 引起页面卡顿。这部分性能相关的在后面会深入讲解的。大家有兴趣也可以看下这篇文章。js防卡顿指北:分帧,并行,空闲执行,延迟执行~

总结下event loop的执行顺序

  1. 解析运行script脚本(同步任务也属于宏任务)
  2. 执行1的过程中遇到定时器 网络请求 promise回调等异步任务。
  3. 将异步宏任务分发给对应的api去执行 等待任务完成 推入任务队列。同时收集微任务推入到任务队列。
  4. 同步的script任务运行完成。(同步任务也属于宏任务)
  5. 从任务队列中取出所有的微任务运行,如果过程中产生新的微任务继续执行,直至微任务队列被清空。
  6. 微任务队列清空后 按顺序取出任务队列中的宏任务执行。
  7. 每执行完一个宏任务后 再次检测微任务队列。
  8. 重复执行5 6 7步骤。

event loop + 事件机制(给用户操作 产生新的任务) 形成了js的任务调度机制。

举个栗子

例子1:

javascript 复制代码
console.log('1');
setTimeout(() => {
    console.log('2');
    Promise.resolve().then(() => console.log('3'));
});
Promise.resolve().then(() => console.log('4'));
console.log('5');

// 先同步任务 1 5 
// 在异步微任务 4 
// 在异步宏任务 2 
// 在注册微任务 3
// 结果:1 5 4 2 3

例子2:

javascript 复制代码
async function async1 () {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2 () {
    console.log('async2');
}
console.log('script start');

setTimeout(function () {
    console.log('setTimeout');
}, 0);
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});
console.log('script end');

这里需要注意两个问题

  1. promise的回调是同步的 then方法的回调才是异步的 这个本质在后续的文章会讲到。
  2. async/await 这里本质上是一个语法糖 换一种方式看着就简单了
javascript 复制代码
function async1 () {
    console.log('async1 start');
    async2().then(()=>{
     console.log('async1 end');
    });
   
}
function async2 () {
    console.log('async2');
}
console.log('script start');

setTimeout(function () {
    console.log('setTimeout');
}, 0);
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});
console.log('script end');

结果:
// script start    
// async1 start  
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

例子3:

javascript 复制代码
const el = document.getElementById("btn")  
el.addEventListener("click", () => {  
  Promise.resolve().then(() => console.log("microtask 1"));  
  console.log("1");  
});  
el.addEventListener("click", () => {  
  Promise.resolve().then(() => console.log("microtask 2"));  
  console.log("2");  
});

// 结果: 1 microtask 1 2 microtask 2

分析下:

  1. 脚本解析完成 同步代码执行完成 注册了两个事件到web api, 进入事件循环阶段。
  2. 当用户点击按钮时 两个点击事件从web api中被推入到任务队列 然后js引擎依次从任务队列中取出任务执行,
  3. 首先检测是否有微任务,没有。执行第一个宏任务,这个任务又注册了异步微任务同时打印了代码1。然后第一个宏任务执行结束。
  4. 检测是否有微任务,有新增的微任务,打印microtask 1。全部微任务执行完成
  5. 执行第二个宏任务回调,又注册了新的微任务,打印代码2。
  6. 检测是否有微任务,有新增的微任务 打印microtask 2,全部微任务执行完成。
  7. 事件循环执行完成 继续等待新任务。

例子4:

javascript 复制代码
const el = document.getElementById("btn");
el.addEventListener("click", () => {
  Promise.resolve().then(() => console.log("microtask 1"));
  console.log("1");
});
el.addEventListener("click", () => {
  Promise.resolve().then(() => console.log("microtask 2"));
  console.log("2");
});
el.click();

结果: 1 2 microtask 1 microtask 2

分析下:

  1. 脚本解析 执行同步代码 注册了两个事件到web api中 然后执行click事件 同步代码并未执行完
  2. click函数依旧在stack内,然后依次执行同步的回调函数
  3. 注册的回调函数依次压入执行栈
  4. 执行第一个回调函数后 生成一个异步微任务
  5. 执行第二个回调函数后 生成第二个异步微任务
  6. 回调函数执行结束 click出栈。
  7. 当前的同步代码执行完成 执行异步代码
  8. 检测有微任务 清空微任务队列。

参考:

  1. developer.mozilla.org/en-US/docs/...
  2. es6.ruanyifeng.com/#docs/promi...
  3. developer.mozilla.org/en-US/docs/...
  4. event loop 在线
  5. 事件循环、dom渲染、宏任务和微任务,他们仨有啥关系?一文全解析!
相关推荐
程序员爱技术1 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
悦涵仙子2 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
兔老大的胡萝卜2 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
cs_dn_Jie6 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic6 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿7 小时前
webWorker基本用法
前端·javascript·vue.js
清灵xmf7 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据8 小时前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_390161778 小时前
防抖函数--应用场景及示例
前端·javascript
334554328 小时前
element动态表头合并表格
开发语言·javascript·ecmascript