参考优质博文:从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!
浏览器分配一个进程(浏览器渲染线程)给这个页面
页面是多线程的:
- http请求会被分配一个异步http请求线程,
- 页面渲染分配一个GUI渲染线程,
- JS代码涉及到JS引擎线程、事件触发线程、计时器触发器线程
- 代码中的setTimeout和setInterval等计时器,会放入计时器触发器线程,到时间了回调函数就被推入执行事件队列
- 事件触发线程是浏览器给JS引擎线程开的外挂,用来控制事件循环的。
- JS引擎线程用于执行JS代码,从事件队列中取事件执行,JS引擎线程和GUI渲染线程是互斥的,所以JS代码如果太拉了,就会影响页面渲染。
强缓存判断,如果缓存失效了的话,进行下面的
- http1.1,在头中添加 Cache-Control 字段,例如:
Cache-Control: Max-Age = 3600
,单位是秒,使用绝对时间,由后台给出,相对于前端时间进行计算,设置为Cache-Control: no-cache
则不使用缓存,直接请求。 - http1.0 头:Expires:值为一个GMT时间,用的是后台的,如果前后台有时差那就会出问题;然后Pragma设置为no-cache的时候就禁用缓存了
- Cache-Control优先于Expires
URL解析:
- 如果host不是ip地址,就拿着host去DNS域名服务器进行查询,中间可能经过路由、缓存
- 拿到了ip地址,准备握手建立连接。
三次握手建立TCP连接
- 客户端:外,哥们,开个门,听到了没,听到了吱一声。(试图确认服务端的正常收发能力)
- 服务端:听到了,超,我听到了,你听到我了吗。(确认了客户端的正常发送能力,试图确认客户端的正常接收能力)
- 客户端:听到了,ok,可以建立连接了。(客户端确认了服务端的正常收发能力)
- 服务端收到最后一次招呼,确认了客户端的正常接收能力,连接建立。
https的话再进行 SSL/TLS 握手
简单版(面试的时候应该背这个的,,,,面试官理解了半天还做了笔记才明白我背的下面的那个随机数版):
- 客户端发出请求,指明自己能使用的hash算法和加密算法列表
- 服务端拿到,挑选一组hash和加密算法,将自己的公钥A+服务端信息进行hash,得到一个摘要,使用ca私钥对摘要进行加密。将加密后的摘要(数字签名) + 明文(公钥A + 服务端信息) + 选择的hash算法和加密算法 = 数字证书 ,发送给客户端
- 客户端拿到,在本地取出ca公钥,对证书进行验证:解密数字签名,拿到一个摘要,使用同样的hash算法对明文进行hash,得到另一个摘要,两个摘要进行对比,如果一致,说明证书有效,服务端的公钥A有效。客户端生成一个对称密钥X,使用服务端公钥A配合指定的加密算法加密对称密钥X,发送给服务端。
- 服务端收到,用服务端私钥解密密文,得到对称密钥X,用X加密一段数据得到密文,并把这段数据进行hash得到一段明文摘要,密文和明文摘要发送给客户端
- 客户端收到,用X解密密文得到明文,用同样的hash算法对明文进行hash得到摘要,这段摘要与刚才收到的明文摘要进行对比,若一致,说明客户端可以确认服务端收到的密钥X是对的,可以建立对称加密。
- 然后进行正常请求。
复杂版:
- 客户端:生成一个随机数A,将随机数A和一系列加密算法发送给服务端
- 服务端:收到,存下A,挑选一组加密算法Z和hash算法,生成一个随机数B,将随机数B和加密算法Z发送给客户端。然后,自己的网址+自己的公钥+证书颁发机构的信息打包 = 证书,发送给客户端。一共两个包,两次发送
- 客户端:收到,验证证书是否值得信任,完事儿拿到服务端的公钥,存下随机数B,自己生成一个随机数C,用服务端公钥和加密算法Z加密C。用A+B+C按照一定方式生成对称密钥X,使用X加密一段明文信息得到密文,然后用约定方式hash明文信息,得到hash段,加密了的C+密文+hash段打包发送给服务端
- 服务端:收到,用私钥和加密算法Z解密得到C,用相同的一定方式将A+B+C生成对称密钥X,用X解密密文得到明文信息,用约定的hash算法hash同一段明文信息得到服务端hash段,同客户端发来的hash段进行比较,一致则说明ok。然后使用X加密一段明文得到密文,并hash这段明文,hash段和密文一同发送给客户端
- 客户端:收到,用X解密密文得到明文,明文进行hash,和服务端发来的hash端进行比较,一致则说明可以建立加密通道。使用X进行对称加密通话。
发出正式的 http 请求
- tcp连接建立了就可以正式发送请求了。
- get请求就只发送一个包,复杂的post的话先发送预检请求,得到status=100后,再发送正式请求,返回status=200的话,ok。
协商缓存判断,如果缓存失效了的话,进行下面的
- 如果上面返回的只是一个返回头,然后返回头的
E-tag
和浏览器请求头的If-None-Match
匹配,说明304,使用本地的协商缓存,http1.1。如果是http1.0,则是服务端Last-Modified
和客户端If-Modified-Since
两个头字段进行匹配。 - E-tag优先于Last-Modefied
后台交互,把资源发给前端
- 后端的,不去了解。
前端拿到返回资源,解析
- 解析HTML,构建DOM树,
bytes-characters-tokens-nodes-DOM
- 转换:bytes->字符
- 分词:字符->词元(有了自己的含义)
- 词法分析:词元->对象(有了自己的属性和规则)
- DOM构建:对象->树形结构(对象之间有了关系)
- 解析CSS,构建CSS规则树,
bytes-characters-tokens-nodes-CSSOM
- 合成DOM树和CSS规则树,生成render树
- 将两棵树合成,样式和DOM元素合成,一些不可见的元素(存在于DOM树中的)就不会进入渲染树
- 布局render树,引起回流(reflow|重排),元素的尺寸、位置计算(这中间可能就有一些JS代码影响了渲染树,导致了回流和重绘)(用本地缓存存储一些访问时会导致回流的变量,可以降低回流的频率,灵活利用transform也可以减少回流)
- 绘制render树(paint),绘制页面像素信息
- 浏览器将各层信息发送给GPU,GPU将各层合成(composite),显示在屏幕上
- 单独的,外链资源的加载:
- img资源,异步下载,不会阻塞各种解析,下载完就直接替换原有src的位置
- css资源,不会阻塞HTML的解析,但是会阻塞渲染树的构建(media query声明的css资源不会阻塞)
- js资源,会阻塞HTML的解析,下载完并执行后才会继续HTML的解析。
- 如果加了defer,下载完之后等待到html解析完了才会执行,是延迟执行;如果加了async,下载完就立即执行,没有先后顺序,谁先下载完谁先执行,所以可能造成js之间的阻塞,是异步下载。
- 有的浏览器也有优化,可以并发下载,但也只是优化下载过程,解析和执行过程的阻塞还是管不着的。
- JS引擎解析(单独拿出来讲):
- 解释阶段
- 词法分析,代码->词元
- 语法分析,词元->语法树
- 使用翻译器将代码转换成字节码(bytecode),谷歌的V8引擎会直接把代码转换成机器码。
- 使用字节码解释器将字节码转换成机器码
- 预处理阶段,为了确保JS代码正确执行
- 变量提升
- 分号补全等
- 执行阶段
- 执行上下文,下面三个都是执行上下文的三个属性;执行堆栈。
- 执行上下文都存在一个栈中
- 浏览器组开始执行JS,创建一个全局执行上下文,压入执行栈
- 然后每进入一个作用域,就创建对应的执行上下文压入栈顶
- 每从一个作用域退出,就把栈顶的执行上下文弹出来,将上下文控制权交给栈顶元素
- 最后都会回到全局执行上下文。
- 执行上下文都存在一个栈中
- VO和AO,对象变量和活动变量:函数中
AO === VO
,全局中AO === this === global
- 对象变量和当前执行上下文有关。
- 活动变量是函数被调用者激活之后出现的。
- 作用域链,类似于原型链,变量的寻找是从内向外的,一层一层,最外面都找不到那就报错
- this,this引出的变量,本层找不到的话不会沿着作用域链找,只和当前上下文有关。
- 执行上下文,下面三个都是执行上下文的三个属性;执行堆栈。
- 回收机制(我看看书再补充)
- 标记清除
- 垃圾回收程序运行,给所有在内存中的变量加上标记
- 变量进入上下文环境后被加上"进入环境"的标记
- 垃圾回收程序运行:把在环境内的所有变量以及其引用的变量都去掉标记
- 变量离开上下文环境之后被加上"离开环境"的标记,这些就是待删除的了
- 垃圾回收程序执行一次内存清理,所有带标记的变量被销毁,被收回内存
- 标记实现方式:变量进入上下文时翻转一位;维护“在上下文中”和“不在上下文中”两个变量列表,列表中的元素可以转移到另一个中
- 引用计数
- 被引用时加一
- 被减持时减一
- 清除引用数为0的变量
- 标记清除
- 解释阶段