进程和线程傻傻分不清楚
只要记住js是单线程的,线程(js)是运行在浏览器这个进程环境中的更小单位。
进程是cpu资源分配的最小单位。
线程是运行在进程基础上的一次程序执行的最小单位,进程可以包含多个进程。
多进程的浏览器
浏览器包含的进程:
1.主进程
只有一个,负责协调和控制。主要作用如下:
- 浏览器的界面展示和交互,比如网页前进和后退
- 控制新建和关闭网页的创建进程和销毁进程
- 负责网页内容的下载和管理等
2.插件进程
每个插件对应一个进程,只有在插件使用的时候才会存在。
3.渲染进程
是绘制页面的主要进程,包含多个线程,比如js加载、事件处理、定时器等
4.GPU进程
负责3D绘制
多进程浏览器的优点
-
防止多个tab、插件崩溃,影响整个浏览器的稳定性
-
效率更高
-
但是也会浪费更多的资源,时间换空间
重点分析渲染进程
渲染进程是对于前端同学来说最熟悉的,也是最重要的,包含js解析执行、事件循环、异步事件等多个线程。
- GUI渲染线程
主要负责页面的布局和绘制。
解析HTML结构,构成DOM树,解析css,构成css ROM树,最后合并成render树
当页面需要重绘和回流的时候,也是GUI线程起作用。
- 定时器线程
之所以不是js线程自己计时,是因为js本身是单线程的,计时就不能执行其他任务,并且计时本身是异步任务,这个时候执行其他同步任务会影响计时的精确性(最主要还是js忙不过来,需要浏览器帮他)。
原理:浏览器单独的线程用来计时,当计时结束后,会将回调函数push进事件队列,等待被执行。
- js引擎线程
负责解析和执行js代码
- 事件处理线程
当js运行到事件处理代码时,会将事件处理交给浏览器的事件处理线程,会在事件发生时(比如点击之后)将回调函数放在js任务队列中等待执行。
- 异步线程
异步请求新开线程单独处理
事件循环
js引擎线程
执行url请求时,会经历以下过程:
URL解析
输入地址进行解析、是否合法、自动补充、安全性检查、是否有缓存
知识点1:js文件代码每次都会变吗?那怎么实现缓存?
知识点2:为什么HTML不支持缓存?
知识点3:URL组成部分?
DNS查询(将url解析成ip地址)
查询缓存顺序:
浏览器dns缓存
查询host
操作系统dns缓存
运营商dns缓存
没有缓存的情况下,将会经历以下过程:
- 主机向
本地DNS服务器发送DNS查询报文(包含想要查询的地址a.b.com) 本地DNS服务器将报文转发到根DNS服务器- 根服务器发现一级域名是com,就会返回一级域名所在的ip地址列表给本地DNS服务器(你可以问下com这个服务器)
本地DNS服务器向其中一个发送查询报文,同理会返回b.com所在的ip地址列表(你去问问b.com)本地DNS服务器向其中一个b.com发送查询报文,这才找到a.b.com所在的ip地址,并且返回给本地DNS服务器- 由此主机获得了ip地址,就可以向该ip地址发送请求。
算法体现:
整个过程用到了递归和迭代两种算法。
主机向本地DNS服务器发送请求,后面的过程都是由本地DNS来完成,并且结果也是从本地DNS获取到,是一个由大问题转化为小问题的过程,是递归的过程。
后面的由本地DNS向各个服务器查询的过程是迭代的过程,都是由本地DNS发起,在本地DNS获取到结果结束。
注:DNS查询不一定全部遵循这个过程,也有可能只有迭代过程或者只有递归过程。
下面这个过程就是只存在递归的过程:
TCP链接(与ip地址所在的服务器建立连接)
三次握手和四次挥手
目的是为了在客户端和服务端之间建立可靠的链接,之所以使用三次握手,是为了双方都确认对方的发送、接收能力正常。
知识点1:三次握手作用
浏览器发送请求、服务器处理请求
浏览器接受响应
包括根据不同的响应头中的状态码去做不同的事。
对资源进行缓存等
这里存在一个知识点:关于浏览器主进程和浏览器渲染进程之间的通信
浏览器主进程接收到响应之后,开启一个下载线程下载资源。将下载完的资源传输给
浏览器渲染进程浏览器渲染进程
开始渲染
页面渲染
接下来开始GUI线程的表演
- 解析HTML,转换成DOM树
解码通过utf解码将二进制文件解码成HTML文件预解析提前加载资源 比如img的src词法分析和构建DOM树同时进行- 完成后,触发
DOMContentLoaded事件
- 解析CSS,转换成css ROM树
- 结合DOM和CSS ROM树生成render渲染树
- 对元素位置、样式信息等进行计算
- GUI线程将渲染信息传输给GPU,由GPU进程将页面绘制在浏览器上
- 触发
load事件
解析HTML的时候遇到js文件,会执行js,DOM构建暂停(js执行会阻碍页面渲染)
如果js引用了未加载的css文件,也不会执行js文件(css会阻碍js加载)
最好将css放在最前面,js放在最后面
重绘和回流 回流是指元素位置等发生变化时,会重新解析HTML,重新渲染一遍,代价较大 重绘则代价较小,只是在元素颜色等发生变化时,只局部更新
js解析执行
接下来是js引擎线程的表演时刻
GUI线程渲染结束后,将执行权交给js引擎线程,开始解析并执行js代码。
- 语法分析阶段
- 词法分析 将代码分成单个词法单元
- 生成AST 将词法单元转换成抽象语法树
- 转成成机器语言
- 预编译阶段
- 变量提升、函数声明提升
- 确定各个执行上下文对象的this
- 确定作用域和作用域链
- 执行代码
js代码的执行过程是单线程的,但是还有辅助它执行的其他线程,共同组成事件循环机制(由事件触发线程管理)
- js引擎执行的同步任务放在
任务栈中 - 当遇到异步任务、定时器任务或者事件触发任务等,浏览器会在事件发生时将回调函数push到一个任务队列中(由事件触发线程管理)
- 当js引擎执行完同步任务之后,会在任务队列中拿出一个任务
添加到任务栈中执行 - 所有在任务栈中执行的任务都被称为
宏任务
执行顺序为:宏任务->渲染->宏任务
es6中,这里涉及到一个名词:微任务(最常见的微任务就是promise,是由js引擎实现的,跟浏览器无关)
在执行宏任务期间积攒的所有微任务,都会在该宏任务执行结束后立即执行所有微任务,然后进行渲染,再执行下一个宏任务(同步任务或者任务队列中取一个)
最终执行顺序为:宏任务->所有微任务->渲染->宏任务
补充:setInterval和setTimeout的区别
setInterval和setTimeout实现的setInterval有什么区别呢?
从事件循环机制上来说,setInterval是确切的每隔time就push一个回调到任务队列,被阻碍的情况下会一下子执行多次。
而setTimeout是在前一个回调执行之后,再间隔至少time才push一个新的回调到任务队列,不存在一下子执行多次的结果。
所以还是推荐用setTimeout实现setInterval。
function myInterval(cb,time) {
function fn() {
let timer;
cb();
clearTimeout(timer)
timer = setTimeout(() => {
fn();
}, time);
}
setTimeout(() => {
fn();
}, time);
}