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的执行顺序
- 解析运行script脚本(同步任务也属于宏任务)
- 执行1的过程中遇到定时器 网络请求 promise回调等异步任务。
- 将异步宏任务分发给对应的api去执行 等待任务完成 推入任务队列。同时收集微任务推入到任务队列。
- 同步的script任务运行完成。(同步任务也属于宏任务)
- 从任务队列中取出所有的微任务运行,如果过程中产生新的微任务继续执行,直至微任务队列被清空。
- 微任务队列清空后 按顺序取出任务队列中的宏任务执行。
- 每执行完一个宏任务后 再次检测微任务队列。
- 重复执行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');
这里需要注意两个问题
- promise的回调是同步的 then方法的回调才是异步的 这个本质在后续的文章会讲到。
- 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
分析下:
- 脚本解析完成 同步代码执行完成 注册了两个事件到web api, 进入事件循环阶段。
- 当用户点击按钮时 两个点击事件从web api中被推入到任务队列 然后js引擎依次从任务队列中取出任务执行,
- 首先检测是否有微任务,没有。执行第一个宏任务,这个任务又注册了异步微任务同时打印了代码1。然后第一个宏任务执行结束。
- 检测是否有微任务,有新增的微任务,打印microtask 1。全部微任务执行完成
- 执行第二个宏任务回调,又注册了新的微任务,打印代码2。
- 检测是否有微任务,有新增的微任务 打印microtask 2,全部微任务执行完成。
- 事件循环执行完成 继续等待新任务。
例子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
分析下:
- 脚本解析 执行同步代码 注册了两个事件到web api中 然后执行click事件
同步代码并未执行完
- click函数依旧在stack内,然后依次执行同步的回调函数
- 注册的回调函数依次压入执行栈
- 执行第一个回调函数后 生成一个异步微任务
- 执行第二个回调函数后 生成第二个异步微任务
- 回调函数执行结束 click出栈。
- 当前的同步代码执行完成 执行异步代码
- 检测有微任务 清空微任务队列。