JS事件循环机制
前置知识之浏览器进程
-
进程:计算机调度的最小单位
-
线程:进程调度的最小单位
-
浏览器进程
- 每一个页面都是一个单独的进程(互不影响)
- 浏览器也有一个主进程(用户界面)
- 渲染进程,浏览器内核为每个页面都分配了一个浏览器进程
- 网络进程(处理请求)
- GPU进程(3d绘制)
- 第三方插件进程
-
渲染进程(包含多个线程)!!!
-
GUI渲染线程(渲染页面)
-
JS引擎线程,与GUI渲染线程执行时机互斥(避免css与js同时操作dom)
-
事件触发线程(独立的线程) -- eventloop!!!
-
事件处理线程,
click、setTimeout、ajax
-
事件处理机制之宏任务、微任务
- 宏任务:由宿主环境(浏览器、node)提供的异步方法,都是宏任务,如script脚本执行、UI渲染
- 微任务:由语言标准(JS)提供的,如
Promise.then、mutationObserver
-
事件机制简述
- 执行首轮宏任务(一般是JS脚本)
- 执行同步任务
- 将微任务放入微任务队列
[先入后出]
。 - 将宏任务放入宏任务队列
[先入后出]
。
- 同步任务执行完毕
清空/执行
微任务队列- 执行同步任务
- 将微任务放入微任务队列
[先入后出]
。 - 将宏任务放入宏任务队列
[先入后出]
。
- 等待本轮宏任务/微任务代码执行造成的UI渲染:(JS执行与GUI渲染共用同一个进程)。
- 开启下一轮宏任务
- 执行首轮宏任务(一般是JS脚本)
-
图解:
code
<!-- - [代码](code/eventloop/demo.html) -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="btn">按钮</div>
<script>
document.body.style.background = 'green';
console.log(1);
Promise.resolve().then(()=>{
console.log(2)
document.body.style.background = 'red';
})
// for(let i =0;i<10000000000000;i++){} 等这个执行完,才执行微任务,然后才渲染页面
console.log(3)
/*
根据图解,浏览器执行顺序
当前宏任务 => 微任务 => GUI渲染引擎(不一定会执行) => 宏任务...
所以第一次修改background时,不会理解渲染页面
只有当微任务执行完毕时才渲染,此时background已是红色,所以直接是红色的页面
*/
// document.body.style.background = 'green';
// console.log(1);
// setTimeout(() => {
// console.log(2)
// document.body.style.background = 'red';
// }, 10);
// console.log(3)
// 当前宏任务 => 微任务 => GUI渲染引擎(不一定会执行) => 宏任务...
// 绿变红,一闪而过
/*
事件与宏任务的关系
如果用户行为,则认为是新的宏任务,点击时将任务放入宏任务队列待执行
如果是代码直接同步执行的,则认为是当前宏任务直接执行
*/
function click1(){
console.log('click1')
Promise.resolve().then(()=>console.log('promise click1'))
}
function click2(){
console.log('click2')
Promise.resolve().then(()=>console.log('promise click2'))
}
let btn = document.getElementById('btn');
btn.addEventListener('click',click1)
btn.addEventListener('click',click2)
btn.click(); // 代码同步调用 => click1() && click2() => click1 、click2 、 promise click1 、promise click2
// 用户点击 => 执行两个宏任务,并在执行完宏任务的时候清除其中的微任务 => click1 、promise click1 、click2 、 promise click2
</script>
<script src="./face.js"></script>
</body>
</html>
interview
// 小菜
new Promise(function(resolve){
console.log('promise1'); // 1
resolve()
}).then(function(){
// 微任务2
console.log('promise2') // 3
})
// 同步
console.log('script end') // 2
// 正菜
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start') // 1
setTimeout(() => { // 宏任务
console.log('setTimeout')
}, 0);
async1()
/**
* 题解:
* 关键: async本质为generator + co => 执行的结果返回值为一个promise
* await => yield => 中断代码,并执行返回的promise.then(),直至返回一个普通值
*
* 分析:
* 默认执行(浏览器情况下,node情况下情况不同,高版本node与浏览器同步了)
* 1.script start
* setTimeout开始计时,计时完毕后放入宏任务中。
* 2 async1 start
* async1()
* await asycn2
* 若async2返回值为promise,则执行其then方法。【并将async1后续逻辑放入then中】
* 若async2返回值不为promise,则将其包装成promise。【并将async1后续逻辑放入then中】
* async2中的代码,正常执行。
* async1 end 后续:被包装到Promise.then()中执行 -- 微任务队列1:
* 3 => async2 返回一个promise并将async1后续的代码放入其then方法中执行 => async1 end 放入微任务队列1
* 4. promise1 => new Promise.then => 放入微任务队列2
* 5. script end
* 6. async1 end // 同步任务执行完毕,执行微任务1
* 7. promise2 // 微任务1执行完毕,执行微任务2
* 8. setTimeout // 微任务执行完毕,执行宏任务1
* 微任务队列 [setTimeout]
* 宏任务队列 [async1 end,promise2]
*
*/