浏览器进程
浏览器是一个多进程的应用程序
-
- 每一个标签页都是一个进程,且互相不影响
-
- 浏览器也有一个主进程:UI(用户界面)
-
- 每一个标签页也都是多进程的:渲染进程、网络进程、GUP进程、第三方插件进程等。
渲染进程
渲染进程包含多个线程
-
- GUI渲染线程
-
- JS引擎线程,与页面渲染时互斥
-
- 事件触发线程,即EventLoop
-
- 定时器监听线程(setTimeout)、HTTP网络请求(ajax)、事件监听线程(click) 也都是单独的线程
EventLoop
EventLoop解决的问题是:JS执行可能会调用异步方法,这些方法是如何调度执行的
JS是单线程运行的,所以大部分代码是同步的,但也有部分异步操作代码,包括了异步微任务和异步宏任务:
异步微任务
- Promise.then/catch/finally
- async/ await
- MutationObserver
- IntersectionObserver
- requestAnimationFrame
- queueMicrotask 手动创建一个异步微任务
- process.nextTick(Node)
异步宏任务
- setTimeout/setInterval
- 事件绑定
- XMLHttpRequest/Fetch
- MessageChannel
JS的异步操作原理是:借用浏览器的多线程机制,再基于EventLoop(事件循环)机制,实现的单线程异步。
EventLoop机制详解
浏览器加载页面,除了开辟堆栈内存外,还会创建两个队列:
-
- WebAPI: 任务监听队列
-
- EventQueue: 事件/任务队列
JS执行时,从上到下执行,如果遇到异步代码,会把异步任务放到任务监听队列中,当异步任务被检测到为可执行时,不是立即执行,而是放到EventQueue中排队等待执行
-
- 根据是微任务还是宏任务,放到不同队列中
-
- 按照队列先进先出的原则,谁先进来的,谁就排在队列的最前面
等到同步代码执行完成,此时主线程空闲下来,此时就会到EventQueue中把正在排队的异步任务取出执行:
-
- 异步微任务优先级较高,只要有可执行的微任务,就先执行。
-
- 同样级别的任务(微任务还是宏任务),谁在队列的前面,谁先执行。
-
- 根据1,每执行完一个宏任务,都会先清空微任务队列,再取出一个宏任务继续执行。
注意:这里的执行是把任务拿到执行栈中执行,而且是交给主线程执行。(所以只要拿出来的这个任务没有执行完,永远不会再去拿其他任务)
例子分析
例子1
console.log('script-start')
new Promise((resolve, reject) => {
console.log('promise1')
setTimeout(() => {
resolve('ok-1')
})
}).then((value) => {
console.log(value)
})
setTimeout(() => {
console.log('setTimeout')
})
new Promise((resolve, reject) => {
resolve('ok-2')
}).then((value) => {
console.log(value)
})
console.log('script-end')
分析:
console.log('script-start'): 同步代码,打印(script-start)- 遇到一个promise,执行函数的内容是同步代码,打印(promise1),遇到 一个setTimeout,放到WebAPI队列,由于,没有设置延时时间,setTimeout会被放到EventQueue队列中的宏任务队列(宏任务1)。而then由于此时还不知道promise的状态(因为setTimeout里面的内容还没有执行,promise的状态还未改变),所以是进入WebAPI任务队列中。
- 遇到 一个setTimeout,放到WebAPI队列,由于,没有设置延时时间,setTimeout会被放到EventQueue队列中的宏任务队列(宏任务1)
- 遇到一个promise,执行函数的内容是同步代码,promise状态已改变,then方法里面的回调函数放到微任务队列中(微任务1)。
console.log('script-start'): 同步代码,打印(script-end)
此时,同步代码执行完,主线程空闲下来,此时就会到EventQueue中把正在排队的异步任务取出执行
- 发现有微任务,先清空,执行了微任务1,打印(ok-2)
- 取出一个宏任务,宏任务1被执行,第一个promise的状态被改变了,then方法放到微任务中
- 发现此时有微任务,执行微任务,打印(ok-1)
- 取出一个宏任务,宏任务2被执行,打印(setTimeout)
所以打印结果是:
script-start
promise1
script-end
ok-2
ok-1
setTimeout
例子2
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');
/**
* script start
* async1 start
* async2
* promise1
* script end
* async1 end
* promise2
* script end
*/