浏览器中的事件循环机制(EventLoop)

我心飞翔 分类:javascript

前言

  • Event Loop即事件循环 是指浏览器或Node的一种解决js单线程运行时不会阻塞的一种机制 也就是我们经常使用异步的原理 为了实现js的异步概念
  • 进程: 计算机分配任务的最小单位
  • 线程: 进程里包含多个线程
  • EventLoop解决的是 js执行时可能会调用异步方法 这些方法是怎样调度执行的

浏览器的进程

  • 每一个tab页都是进程 (tab页互不影响)
  • 浏览器也有一个主进程 (用户界面)
  • 网络进程 (处理请求)
  • GPU进程 3d 绘制
  • 第三方插件的进程
  • 渲染进程 每个tab页里 都有一个渲染进程 (浏览器内核)

渲染进程

  • 包含着多个线程
  • GUI渲染线程 用来渲染页面
  • JS引擎线程 他和页面渲染时互斥
  • 事件触发线程 独立的线程 EventLoop
  • 事件 click setTimeout ajax也是一个独立线程

流程

  • 如下图示
  • 宏任务 如: setTimeout setInterval postMessage MessageChannel setImmediate...
  • 微任务 如: promise.then mutationObserver
* js执行的时候 会从上到下执行
* 遇到函数会创建执行上下文 放入到执行栈中
* 执行完毕后会出栈 执行时可能会发生异步事件(内部会调用浏览器Api 如: `setTimeout`, `ajax`...)

* 当我们执行上下文栈都执行完毕后 等会可能api(如 `setTimeout`)执行完成或者时间到达
* 会被维护到一个事件队列中 (原则先进先出) 

* 不停的扫描队列 将队列里的任务拿出来放到上下文栈中执行 
* 事件循环线程是专门干这件事的 检测当前执行栈是否为空 如果为空 从事件队列中取出**一个来执行** (如`setTimeout(宏任务)`)

* 当代码执行时还会有一些任务 以`promise.then(微任务)`为例
* 每次执行宏任务的时候 都会单独创建一个 微任务队列 (原则先进先出)

* 微任务在执行完毕后 浏览器会检测是否要重新渲染 浏览器有刷新频率 大约16.6ms
* 小于这个时间 浏览器是不会渲染的

* 每次循环一次都会执行一个宏任务 并清空对应的微任务队列 
* 微任务中在执行时再生成微任务 会在本轮直接清空
* 每次循环完毕后 都要看是否要渲染 如果需要渲染才渲染

 

图片替换文本

示例

微任务和GUI渲染

// 页面的背景色 有没有从红到黄
// 执行栈       [console.log(1) console.log(3)]
// 微任务队列    [Promise.then]
// 执行完宏任务 清空微任务
// 再去渲染 页面没有从红到黄, 直接就是黄色

// log: 1 3 2
document.body.style.background = 'red';

console.log(1)
Promise.resolve().then(()=>{
    console.log(2)
    document.body.style.background = 'yellow';
})
console.log(3);

 

事件任务

<button id="button">事件按钮</button>
 
// 第一种
// 当前未点击button按钮 直接在下 button.click() 输出的结果是什么
// 当前两个事件在执行栈中执行
// 执行栈       ['click1'  'click2']
// 微任务队列    [Promise.then1  Promise.then2]

// log: 'click1'  'click2' 'c1-micro-task1' 'c2-micro-task2'
button.addEventListener('click',()=>{ // 1
    console.log('click1');
    Promise.resolve().then(()=>console.log('c1-micro-task1'))
})
button.addEventListener('click',()=>{ // 2
    console.log('click2');
    Promise.resolve().then(()=>console.log('c2-micro-task2'))
})
button.click();
 
// 第二种
// 当前点击button按钮 输出的结果是什么
// 上面流程我们说道
// 事件循环线程是专门干这件事的 检测当前执行栈是否为空 如果为空 从事件队列中取出**一个来执行**
// 一个一个拿到执行栈执行
// 宏任务队列 [addEventListener1  addEventListener2]

// 先拿出 addEventListener1, 并清空微任务队列 [Promise.then1]
// 再拿出 addEventListener2, 并清空微任务队列 [Promise.then2]

// log: 'click1' 'c1-micro-task1'  'click2'  'c2-micro-task2'
button.addEventListener('click',()=>{ // 1
    console.log('click1');
    Promise.resolve().then(()=>console.log('c1-micro-task1'))
})
button.addEventListener('click',()=>{ // 2
    console.log('click2');
    Promise.resolve().then(()=>console.log('c2-micro-task2'))
})
 

定时器任务

// 执行栈执行 有定时器 时间以到 放到 宏任务队列 [setTimeout2]
// 执行栈执行完
// 接着清空微任务队列[Promise.then1]

// 微任务有定时器 时间以到 放到 宏任务队列 [setTimeout2 setTimeout1]

// 最后一个一个宏任务 执行

// log: Promise1 setTimeout2 Promise2 setTimeout1
Promise.resolve().then(() => { // 1
    console.log('Promise1')
    setTimeout(() => {
        console.log('setTimeout1')
    }, 0);
})

setTimeout(() => { // 2
    console.log('setTimeout2');
    Promise.resolve().then(() => {
        console.log('Promise2')
    })
}, 0);
 

一道有意思的面试题

  • await 会暂停async 函数内后面的代码
  • 先执行 async 函数外的同步代码
  • 如果 await 的是 Promise 对象
  • 等着 Promise 对象 的状态是status = 'FULFILLED'
  • 然后把 resolve 的参数作为 await 表达式的运算结果返回后
  • 再继续执行 async 函数内后面的代码
// log: 1 6 2 3 8 7 4 5

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);
 

回复

我来回复
  • 暂无回复内容