阅读 39

1. eventloop

浏览器事件环

  • 进程: 计算机分配任务和调度任务的最小单位, 浏览器是一个多进程模型
  • 线程: 进程的一个单位
  • 整个浏览器在渲染一个网页的时候有哪些线程?
    • 首先每个页卡都有一个渲染进程(浏览器内核)
    • 渲染进程包含的线程有:
    1. GUI渲染线程 页面渲染 绘图 绘制 3d动画
    2. js引擎 执行js代码 执行js代码的时候渲染线程会挂起 互斥
    3. 事件触发线程 eventloop
    4. webapi 也会创建线程, 事件/定时器/ajax请求都会创造单独的线程
    • 所谓的js是单线程指的是主线程是单线程

EventLoop

  • 解决的是 js 执行时可能会调用异步方法,这些方法是怎么调度执行的
  • 由单独的线程不停的循环扫描
  1. js 执行的时候会从上到下执行,遇到函数就创建执行上下文放入执行栈中,执行完毕后会出栈,执行过程中可能会发生异步事件(内部会调用浏览器 api)
  2. 当执行上下文栈都执行完毕后,等到 api 执行完成或者时间到达,回调函数会被放到事件队列当中(先进先出)
  3. 单独的线程不停的扫描队列,将队列中的任务拿出来放到上下文栈中执行 事件循环线程是专门用来干这件事情的,检测当前执行栈是否为空,如果为空,从事件队列中取出一个来执行(如 setTimeout 宏任务)
  4. 当代码执行时还会有一些任务 promise 为例,微任务,每次执行宏任务的时候 都会单独创建一个微任务队列 先进先出
  5. 微任务在执行完毕后 浏览器会检测是否要重新渲染 浏览器有刷新频率 大约 16.6ms
  6. 每次循环一次都会执行一个宏任务并清空对应的微任务队列,每次循环完毕后,都要看是否要渲染,如果需要渲染才渲染
  • 小结:js 代码开始执行,这就是一个宏任务,执行完毕后会把本轮的微任务清空,清空完以后可能会发生页面渲染(页面可能渲染可能不渲染),之后看宏任务有没有,有的话从宏任务中取出来一个接着执行,执行的时候可能又产生了微任务,可能又产生宏任务,宏任务队列只有一个,微任务队列每轮循环都会有一个新的,每一个 宏任务都有自己的一个微任务队列 , 再清空,再看需不需要渲染,再取一个宏任务。。。这个流程就转起来了, 这件事情就是 eventloop 做的
  • 宏任务 script 脚本 界面渲染也是宏任务 setTimeout setInterval postMessage MessageChannel setImmediste 也是宏任务 事件 ajax
  • 微任务 promise.then mutationObserver
  • 微任务中执行时再生成的微任务会在本轮直接清空
  • 微任务中产生的宏任务继续放到宏任务队列,等待一个一个被取出执行

EventLoop 相关题目

  • 微任务和 GUI 渲染

浏览器渲染是每一轮eventloop执行的末尾去检测是否要渲染,每一轮只有一个宏任务

<script>
document.body.style.background = 'red';
console.log(1)
Promise.resolve().then(()=>{
console.log(2)
document.body.style.background = 'yellow';
})
console.log(3);
</script>
// 执行顺序: 1/3/2
// 颜色变化: 只会显示黄色 -- 微任务执行在界面渲染之前

// 如果将微任务改成定时器 setTimeout(() => {}, 0)
// 颜色变化: 有可能是由红变黄 有可能一直是黄 (因为浏览器有刷新频率)
<script>
document.body.style.background = 'red';
console.log(1)
setTimeout(()=>{
console.log(2)
document.body.style.background = 'yellow';
},0)
console.log(3);
</script>
复制代码
  • 事件任务
/*
注意:click事件是宏任务, 但是如果在代码中直接这样执行button.click(),并不会产生宏任务
并不是用户点击触发的,而是在脚本中直接触发, 就相当于执行这两个方法,并不是异步的
*/
<script>
button.addEventListener('click',()=>{
console.log('listener1');
Promise.resolve().then(()=>console.log('micro task1'))
})
button.addEventListener('click',()=>{
console.log('listener2');
Promise.resolve().then(()=>console.log('micro task2'))
})
button.click(); // click1() click2()
</script>
// 输出: listener1 / console.log('listener2') / micro task1 / micro task2
// 如果是点击按钮触发,输出: listener1 / micro task1 / listener2 / micro task2
复制代码
  • 定时器任务
<script>
Promise.resolve().then(() => {
    console.log('Promise1')
    setTimeout(() => {
        console.log('setTimeout2')
    }, 0);
})
setTimeout(() => {
    console.log('setTimeout1');
    Promise.resolve().then(() => {
        console.log('Promise2')
    })
}, 0);
</script>
// 输出: Promise1 / setTimeout1 / Promise2 / setTimeout2
复制代码
  • 任务执行面试题

async执行后返回的是一个promise

await后面包装的内容 await console.log(3); => 相当于 return Promise.resolve(console.log(3)).then(()=>{console.log(4)})

await console.log(3) 会让3立即执行并把await下面的代码延迟到下一个微任务中去执行

console.log(1);
async function async() {
  console.log(2);
  await console.log(3);
  console.log(4);
}
setTimeout(() => {
  console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
  console.log(6);
  resolve(7);
});
promise.then((res) => {
  console.log(res);
});
async();
console.log(8);

// 输出:1-6-2-3-8-7-4-5
复制代码
Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res);
})
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(()  => {
    console.log(5);
})

// 输出: 0-1-2-3-4-5
/**
 当返回一个promise不会立刻处理这个promise,会将这个promise放到异步方法中进行处理
(再次新生成一个微任务)[return Promise.resolve(4);会产生两次then] 
*/ 

复制代码
文章分类
前端
文章标签