进程
启动一个程序时,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这个运行环境叫进程。
线程
是进程内的一个独立执行单元,一个进程中可以有多个线程,多个线程共享进程的数据。进程中的任意一线程执行出错,都会导致整个进程的崩溃。
管道
: 本质上就是内核中的一个缓存,一个进程往管道中写,另一个进程去管道中读。消息队列
:A进程往消息队列写入数据后就可以正常返回,B进程需要时再去读取就可以了,效率比较高。共享内存
: 系统加载一个进程的时候,分配给进程的是虚拟内存空间。共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。Socket
:Socket是操作系统提供给程序员操作网络的接口内核态和用户态
用户态与内核态的概念就是CPU指令集权限的区别。
CPU指令集
操作的权限由高到低划为4级
ring 0 可以使用所有 CPU 指令集,
ring 1
ring 2
ring 3 仅能使用常规CPU指令集,不能使用操作硬件资源的 CPU 指令集,比如 IO 读写等
ring 0 被叫做 内核态,完全在操作系统内核中运行,由专门的内核线程在 CPU 中执行其任务
ring 3 被叫做 用户态,在应用程序中运行,由用户线程在 CPU 中执行其任务
浏览器进程
:主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。渲染进程
:核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页
JS引擎线程
:JavaScript引擎V8,负责处理JavaScript脚本程序。依靠任务队列来进行js代码的执行,所以js引擎会一直等待着任务队列中任务的到来,然后加以处理。GUI渲染线程
:负责渲染浏览器界面,解析 HTML,CSS,构建render树,布局和绘制等。计时器线程
:因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响计时的准确。当使用setTimeout或者setInterval时,需要定时器线程计时。计时到了之后,将对应的回调放入事件队列中,等待JS执行。异步http请求线程
:负责异步请求管理,XMLHttpRequest在连通后通过浏览器新起一个线程请求。检测到状态变化时,如果有设置回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,等到JS执行事件触发线程
:控制事件循环,比如JS执行遇到计时器,AJAX异步请求等,就会将对应任务添加到事件触发线程中,在对应事件符合触发条件触发时,就把事件添加到待处理队列的队尾,等JS引擎处理。GPU进程
: GPU的使用初衷是为了实现3D CSS的效果,只是随后网页、Chrome的UI界面都选择采用GPU来绘制。网络进程
:主要负责页面的网络资源加载。插件进程
: 主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响JS是单线程还是多线程?
JS是单线程运行的,但使用H5中的Web Workers可以多线程运行
方法 | 通讯范围 | 优缺点 |
---|---|---|
localStorage | 相同浏览器的同源窗口都会共享 可以使用 storage 事件监听localStorage变化 |
优点: 操作简单,易于理解 缺点: 存储大小限制,使用范围是相同浏览器的同源窗口 |
websocket | 与服务器建立websocket连接的页面 如果pageA更改了数据,那么向服务端发送一条消息或数据,服务端在将这条消息或数据发送给pageB即可,这样就简单实现了两个标签页之间的通信 |
优点: 理论上可是实现任何数据共享跨域共享 缺点: 需要服务端配合增加服务器压力 |
sharedWorker | sharedWorker就是webWorker中的一种,它可以由所有同源页面共享 sharedWorker的原理和websocket有点类似,都是广播和接收的原理 |
优点:没有大小限制 缺点: 跨域不共享 调试不方便 兼容性不好 |
–
参考文章
是什么?
Web Workers 是Html5提供的一个多线程解决方案,Web Workers可以在独立于主线程的后台线程中,运行一个脚本操作。但是该脚本程序不能操作DOM,主要用于计算
有什么用?
可以在独立线程中执行费时的处理任务,避免JS引擎线程阻塞线程渲染视图。
特点
1.主线程postMessage通知worker线程
2.worker线程onMessage方法接收到消息,去安排工作,完成工作后,用postMessage方法通知主线程
3.主线程onMessage方法监听消息
应用场景
适合做非常耗时的计算工作
是什么?
JS是单线程,当遇见异步任务时,在执行异步任务时需要等待任务完成,浪费大量的时间。所以将异步任务交给任务队列中,当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务,将其取出执行。整个执行过程,我们称为事件循环过程。
由于所有任务的优先级不一样,将任务分为微任务和宏任务。这种设计是为了给紧急任务插队的机会,否则新入队的任务永远被放在队尾。
微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。所以有些时候也有人将同步代码看成一个宏任务。
事件循环机制主要的作用
事件循环机制用于管理异步回调函数什么时候回到主线程中执行
宏任务
微任务
这里DOM渲染的时机可以先说完循环机制再添加
DOM的修改不会立马导致渲染,渲染线程和Javascript线程是互斥的,必须等待Javascript的这次调度执行完或线程挂起了,才能执行渲染。
这次调度可以看成是一轮事件循环完,一次事件循环=宏任务(第一次是同步代码)+微任务
显示器的刷新频率是60Hz,浏览器也会尽量保持60Hz的刷新率运行,也就是16.7ms刷新一帧所以 浏览器渲染的触发并不是每次事件循环都会触发,他会以大约60帧每秒的频率来触发。如果一次循环内没有触发render,那requestAnimation回调也会执行
浏览器和nodejs是不同的JS运行环境
1.V8引擎负责解析和执行JavaScript代码
2.内置API是由运行环境提供的特殊接口,只能在所属的运行环境中被调用
node:v8引擎将js代码分析后去调用对应的node api,而这些api最后则由libuv引擎驱动,执行对应的任务,并把不同的事件放在不同的队列中等待主线程执行。 因此实际上node中的事件循环存在于libuv引擎中。
node的事件循环机制是处理非阻塞 I/O 操作的机制。
将整个流程分为多个阶段,每个阶段都有一个先进先出执行回调的队列,不同的事件放在不同的队列中等待主线程执行。
//事件循环的每一轮循环,会按照下图给定的优先级顺序进入七个阶段的执行,
/*
┌───────────────────────────┐
┌─>│ timers │ -> 定时器,延时器的执行
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ -> i/o
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
*/
process.nextTick
process.nextTick 是一个独立于 eventLoop 的任务队列。
在每一个 eventLoop 阶段完成后会去检查 nextTick 队列,如果里面有任务,会让这部分任务优先于微任务执行。
总结
node11 一旦执行完一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)就立刻执行对应的微任务队列。
在 node11 之前执行完一个阶段后才开始执行微任务。
浏览器和Node环境下事件循环的区别
练习题1
执行同步代码
setTimeout 1ms后将回调放入到timer阶段
setImmediate回调注册到check阶段
sleep(1000); 阻塞线程1s秒
之后开始事件循环,此时定时器已经到事件了
所以先输出1,后输出2
setTimeout(()=>console.log("1"),0);
setImmediate(()=>console.log("2"));
function sleep(delay){
let start = new Date().getTime();
while(new Date().getTime()-start < delay){
continue;
}
}
sleep(1000); //阻塞线程1s秒
练习题2
执行同步代码
setTimeout 1ms后回调注册到timer阶段
setImmediate回调注册到check阶段
然后进入事件循环,但是此时并不知道是否已经过了1ms,所以输出是不确定的
setTimeout(()=>console.log("1"),0);
setImmediate(()=>console.log("2"));
在nodejs中, setTimeout(demo, 0) === setTimeout(demo, 1)
在浏览器里面 setTimeout(demo, 0) === setTimeout(demo, 4)
练习题3
先执行同步代码,将fs.readFile的回调函数放入poll轮询中。
进入事件循环当中,从timer阶段开始,到poll轮询阶段,执行回调函数,将setImmediate回调放入check阶段,将setTimeout回调放入timers回调。poll队列为空,发现check阶段有回调函数,进入check阶段,输出setImmediate。进入下一轮事件循环进入timers阶段,输出setTimeout
var path = require('path');
var fs = require('fs');
fs.readFile(path.resolve(__dirname, '/read.txt'), () => {
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate');
})
});
练习题4
开始异步读文件,然后执行setImmediate,读文件成功后放入poll队列等待执行
var fs = require('fs');
fs.readFile(__filename, () => {
console.log('poll');
})
setImmediate(() => {
console.log('immediate');
});
/*
immediate
poll
*/
**重点:将async/await改写成promise **
知识点1: await是.then的语法糖。await A函数表示先执行A函数、A函数返回一个promise对象,通过.then获取promise的结果。 函数的执行是同步的,但是获取promise的结果是异步的。
知识点2:await是一个让出线程的标志, await函数后面的代码,需要等到await函数执行完毕后才执行。await修饰的函数执行完毕后,会跳出async修饰的函数,执行其他代码。
async function async1() {
await async2();
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1();
//改写后
function async1() {
//await函数前的代码
new Promise(
(resolve)=>{console.log('async2 end')}//await后面的函数
)
.then(//await函数后的代码
res=>console.log('async1 end')
)
}
练习题1
console.log('script start')
async function async1() {
await async2()
console.log('async1 end'); //放入微队列①
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout'); //放入宏队列①
}, 0)
new Promise(resolve => {
console.log('Promise');
resolve()
})
.then(function() {
console.log('promise1'); //放入微队列②
})
.then(function() {
console.log('promise2')//放入回调函数数组中存起来,等待前一个.then的结果
})
console.log('script end')
/*
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout
*/
面试题:微任务会一次性清空一个,还是全部清除?执行微任务的过程中又产生了微任务会什么时候执行?
根据promise1
、promise2
、setTimeout
的输出顺序可以得出答案: 全部清除,产生了微任务后放入微队列,等待前面微任务执行完毕后出队执行
练习题2
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {//放入宏队列1
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => { //回调函数被保存,等待promise的状态改变后执行
console.log(res);
});
console.log(4);
/* 1 2 4 timerStart timerEnd success */
练习题3
setTimeout(() => {
console.log("0") //宏队列1
}, 0)
new Promise((resolve,reject)=>{//①
console.log("1") //同步代码输出
resolve() //状态改变 先改变状态再执行回调
}).then(()=>{ //微队列1 //①.then
console.log("2")
new Promise((resolve,reject)=>{//②
console.log("3")
resolve()//状态改变
}).then(()=>{ //微队列3 //②.then
console.log("4")
}).then(()=>{ //微队列5 .then是同步的先指定回调会缓存起来,先指定回调后改变状态
console.log("5")
})
}).then(()=>{
console.log("6") //微队列4
})
new Promise((resolve,reject)=>{
console.log("7") //同步代码输出
resolve() //状态改变
}).then(()=>{ //微队列2
console.log("8")
})
//1 7 2 3 8 4 6 5 0
容易出错的是6和5的顺序
4的.then先执行了,4放入微队列,但是5的.then是同步执行的,也就是此时先指定回调函数,再改变状态,所以5的.then里面的回调会缓存起来,等4的状态改变时再执行。
在下面添加了一个6.then会更清晰。
new Promise((resolve,reject)=>{
console.log("1")
resolve()
}).then(()=>{
console.log("2")
new Promise((resolve,reject)=>{
console.log("3")
resolve()
}).then(()=>{
console.log("4")
}).then(()=>{
console.log("5")
reject(8);
}).then(()=>{
console.log("6")
})
}).then(()=>{
console.log("7")
})
//123475
对于4.then把回调函数放入微队列,同步执行5.then,缓存回调函数,同步执行6.then缓存回调函数。相当于内层的promise已经执行完了,状态是成功,6.then只会影响4.then.then的结果,对于内层promise来说,同步代码执行完后没有抛出异常或者调用resolve、reject,此时内层promise就是成功。同步执行7.then,7.then的回调函数放入微队列
练习题4
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('setTimeout0')
process.nextTick(() => console.log('nextTick3'));
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
15-16不是绝对的,需要看执行前面执行的时间有没有超过300ms