JS执行机制
setTimeout中的回调函数一定会准时执行吗?
const start = Date.now()
setTimeout(()=>{
const end = Date.now()
console.log(end-start)
}, 500)
// async function delay(num){
// if(num>100000) return
// await Promise.resolve().then(()=>delay(++num))
// }
// delay(1)
进程与线程
什么是进程?
- 进程是系统进行资源分配的基本单位
- 进程是程序的基本执行实体,可以理解为一个可以独立运行且拥有自己资源空间的任务程序
- 进程之间相互隔离,互不干扰
什么是线程?
- 线程是任务调度和执行的基本单位
- 一个进程中可以并发多个线程,每个线程并行执行不同的任务
- 同一进程下的线程之间可以直接通信和共享数据
单线程、多线程与多进程
单线程:是指程序中仅包含一个执行流,程序将会按照执行流顺序执行。
多线程:是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务
多进程:多个拥有自身资源空间且能独立运行的任务程序
举个栗子
单线程、多线程、多进程
JS单线程
所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。
JS为什么是单线程?
JS的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户交互,以及操作DOM。假如JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
JS如何处理延时任务?
既然JS是单线程的,那么在同一时间应该只能执行一个任务。假如当前存在一个任务需要等待五秒后才能拿到结果输出,那么JS会等待五秒后再执行后续任务吗?显然不会。
同步与异步
同步:
指的是代码当中的任务将会按照代码编写顺序依次执行,后一个任务必须等待前一个任务执行完毕才会执行。
例如,银行服务窗口
异步:
指代码执行过程中,不会等待一个任务执行完成再执行另一个任务,异步任务开启过后就立即往后执行下一个任务,耗时任务的后续逻辑一般会通过回调函数的方式定义,当耗时任务完成后再执行回调函数。
浏览器中的进程与线程
浏览器中主要包括哪些进程?
浏览器主进程:
负责控制浏览器除标签页外的界面,包括地址栏、书签、前进后退按钮等,以及负责与其他进程的协调工作
GPU进程:
负责整个浏览器界面的渲染,在 浏览器显示 GPU 加速内容时使用的进程。 浏览器会用 GPU 来加速网页渲染、典型的 HTML、CSS 和图形元素。
插件进程:
主要负责插件的运行,因为插件可能崩溃,所以需要通过插件进程来隔离,以保证插件崩溃也不会对浏览器和页面造成影响
网络进程:
主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器主进程里面,后续独立出来,成为一个单独的进程
渲染进程(浏览器内核):
负责控制显示tab标签页内的所有内容,核心任务是将HTML、CSS、JS转为用户可以与之交互的网页,排版引擎Blink和JS引擎V8都是运行在该进程中,默认情况下Chrome会为每个Tab标签页创建一个渲染进程
渲染进程
GUI渲染线程:
负责渲染页面,解析html和CSS、构建DOM树、CSSOM树、渲染树、和绘制页面,重绘重排也是在该线程执行
JS引擎线程:
负责解析和执行Javascript脚本程序。
注意:GUI渲染线程与JS引擎线程是互斥的,js引擎线程会阻塞GUI渲染线程。
事件触发线程:
主要用来控制事件循环,比如JS执行遇到计时器,AJAX异步请求等,就会将对应任务添加到事件触发线程中,在对应事件符合触发条件触发时,就把事件添加到待处理队列的队尾,等JS引擎处理
定时器线程:
因为JS引擎是单线程的,所以如果处于阻塞状态,那么计时器就会不准了,所以需要单独的线程来负责计时器工作
异步HTTP请求线程:
XMLHttpRequest连接后浏览器开的一个线程,比如请求有回调函数,异步线程就会将回调函数加入事件队列,等待JS引擎空闲执行
宏任务与微任务
在JavaScript中,事件队列中的任务被分为两种:宏任务(Macrotask)和微任务(Microtask),他们分别会被添加到宏任务队列和微任务队列。
为什么要区分宏任务和微任务?
这种设计是为了给紧急任务一个插队的机会,否则新入队的任务永远被放在队尾。区分了宏任务和微任务后,本轮循环中的微任务实际上就是在插队,这样微任务中所做的状态修改,在下一轮事件循环中也能得到同步。例如,银行大爷办理额外业务
宏任务:
-
Script(整体代码)
-
setTimeout
-
setInterval
-
setImmediate(用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数,类似setTimeout(fn,0)—已废弃)
-
I/O操作
-
UI渲染
微任务:
- Promise.then、.catch、.finally
- async/await
- MutationObserver(html5新特性,监听DOM树结构改变,作出相应处理)
- process.nextTick(Node)
- queueMicrotask
事件循环机制
概述:
由于js执行引擎只有一个主线程执行代码逻辑,遇到需要异步执行的任务代码,会将其添加到事件队列中,等待主线程空闲时执行。
事件队列中的任务分为宏任务和微任务,他们会被添加到不同的事件队列中(宏/微任务队列)。
当主线程将同步代码执行完毕后,会先将其产生的的微任务推入主线程执行,如果微任务执行过程中又产生了新的微任务,新产生的微任务会被添加到微任务队列末尾,按照队列先进先出原则继续执行。当微任务队列被清空后,会执行下一个宏任务,再清空宏任务产生的微任务,不断循环。
警告: 因为微任务自身可以入列更多的微任务,且事件循环会持续处理微任务直至队列为空,那么就存在一种使得事件循环无尽处理微任务的真实风险。如何处理递归增加微任务是要谨慎而行的。 ------MDN
图解:
JavaScript整体代码在执行中按顺序从上往下编译执行
执行同步代码,遇到异步任务将其添加到宏微任务队列
执行栈中的代码执行完毕,执行栈处于空闲状态,先将微任务队列清空
微任务执行过程中,产生新的微任务,添加到队列末尾
清空微任务队列
执行下一个宏任务,将产生的微任务添加到微任务队列
宏任务执行完毕,清空微任务队列
执行下一个宏任务,任务队列全部清空,结束
练习:
Test1:
console.log('start');
setTimeout(() => {
console.log('T1');
new Promise(resolve => {
console.log('P1');
resolve();
})
.then(() => {
console.log('P1-then');
})
}, 0);
new Promise(resolve => {
console.log('P2');
resolve();
})
.then(() => console.log('P2-then'));
console.log('end');
// start
// P2
// end
// P2-then
// T1
// P1
// P1-then
Test2:
console.log(1)
setTimeout(() => {
console.log(4)
new Promise(resolve => {
console.log(5)
resolve()
})
.then(() => console.log(6))
}, 0)
new Promise(resolve => {
console.log(2)
resolve()
})
.then(() => console.log(3))
setTimeout(() => {
console.log(7)
new Promise(resolve => {
console.log(8)
resolve()
})
.then(() => console.log(9))
}, 0)
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
Test3:
async function async1() {
console.log("A1-start");
await async2();
console.log("A1-end");
}
async function async2() {
console.log("A2");
}
console.log("start");
setTimeout(function () {
console.log("T1");
}, 0);
async1();
new Promise(function (resolve) {
console.log("P1");
resolve();
}).then(function () {
console.log("P1-then");
});
console.log("end");
// start
// A1-start
// A2
// P1
// end
// A1-end
// P1-then
// T1
);
setTimeout(function () {
console.log(“T1”);
}, 0);
async1();
new Promise(function (resolve) {
console.log(“P1”);
resolve();
}).then(function () {
console.log(“P1-then”);
});
console.log(“end”);
// start
// A1-start
// A2
// P1
// end
// A1-end
// P1-then
// T1