浏览器的进程模型
-
进程:程序运行需要有它专属的内存空间,可以把这块内存空间简单的理解为进程。每个应用至少有一个进程,进程之间相互独立,即使要通信,也需要双方同意。
-
线程:一个进程至少有一个线程,进程开启后会自动创建一个线程,该线程称为主线程。如果程序需要同时执行多块代码,主线程就会启动更多的线程来执行代码,一个进程中可以包含多个线程。
-
浏览器是多进程多线程的,当浏览器启动时,为了减少连环崩溃的几率,会自动启动多个进程,其中最主要的进程有:
- 浏览器进程:主要负责界面显示(非网页)、用户交互(非网页内)、子进程管理等。该进程会启动多个线程处理不同的任务。
- 网络进程:负责加载网络资源,该进程会启动多个线程处理不同的网络任务。
渲染进程:渲染进程启动后会开启一个渲染主线程,负责执行HTML、CSS、JS代码。默认情况下,浏览器会为每一个标签页开启一个新的渲染进程,以保证不同标签页之间不相互影响。
同步与异步
同步
- JS是一门单线程的语言,因为它运行在渲染主进程中,而渲染主线程在每个标签页中只有一个,意味着同一时间只能做一件事。渲染主线程要执行解析HTML、解析CSS、执行JS等,如果使用同步的方式执行计时任务、网络通信、事件监听等任务时,就会导致主线程阻塞,从而影响消息队列中其它任务执行,给用户造成卡死现象。
异步
- 当执行到计时器、网络通信、事件监听任务时,主线程会将这些任务交给一个新线程去处理,自身立即结束任务的执行,继续执行消息队列中的任务。当其他线程完成时会通知主线程,将回调函数包装成任务加入到消息队列的末尾,等待主线程执行。这样浏览器就永不会阻塞,保证了单线程的流畅运行。
异步任务
宏任务
- <script>代码块
- 定时器 setTimeout、setInterval
- 事件监听 addEventListener
- ajax
- 回调函数
微任务
- Promise(Promise是同步任务,执行resolve或reject时是异步操作)
- MutationObserver(会在指定的DOM发生变化时被调用)
- process.nextTick
执行栈
- 当一个函数被调用之前,JS会解析这段代码,创建一个函数执行上下文(执行环境),将其中
同步代码按照执行顺序加入到执行栈中,然后从栈顶开始执行。当函数执行结束时,JS会退出当前函数的执行环境,对应的执行上下文销毁,然后回到上一个执行环境继续执行,直到执行栈中的代码全部执行完毕。
事件循环与消息队列
-
当遇到异步代码时,JS并不会一直等待其返回结果,而是将其交给其他子线程去处理,主线程会继续执行执行栈中的同步代码。
-
当子线程接收到处理结果后,会将对应的回调函数放置到对应的消息队列(宏任务队列、微任务队列)中,等待执行。
-
当执行栈中的任务全部执行完毕或遇到空闲时间时,主线程开始检查
微任务队列,按照顺序执行队列中的所有微任务。 -
当微任务执行完毕后,主线程检查
宏任务队列,取出第一个宏任务进行执行。 -
当宏任务执行完毕后,再次检查微任务队列,如果有微任务继续执行所有微任务。
-
执行下一个宏任务……循环往复……
以上整个过程叫做
事件循环。
代码解析
// 位置 1
setTimeout(function () {
console.log('timeout1');
}, 1000);
// 位置 2
console.log('start');
// 位置 3
Promise.resolve().then(function () {
// 位置 5
console.log('promise1');
// 位置 6
Promise.resolve().then(function () {
console.log('promise2');
});
// 位置 7
setTimeout(function () {
// 位置 8
Promise.resolve().then(function () {
console.log('promise3');
});
// 位置 9
console.log('timeout2')
}, 0);
});
// 位置 4
console.log('done');
// 1. 位置1为异步宏任务,1s后放入宏任务队列
// 2. 打印位置2 start
// 3. 位置3 then() 为异步微任务,放入微任务队列
// 4. 打印位置4 done
// 5. 执行微任务队列,打印位置5 promise1
// 6. 位置6 then() 为异步微任务,放入微任务队列
// 7. 位置7为异步宏任务,放入宏任务队列
// 8. 执行微任务队列,打印位置6 promise2
// 9. 执行宏任务队列,将位置8 then()放入微任务队列,打印位置9 timeout2
// 10.执行微任务队列,打印位置8 promise3
// 11.1s后,执行宏任务,输出timeout1