看了别人的文章,更改删除了部分内容,以适应自己的学习需要,非常感谢撒网要见鱼的分享。
主干流程:
一、从浏览器接收URL到开启网络请求线程(这一部分可以展开浏览器的机制以及进程与线程之间的关系,JS的运行机制)
1、多进程的浏览器:(浏览器是多进程的)
- 1、有一个主控进程(负责协调、主控,只有一个),
- 2、第三方插件进程(每种类型的插件对应一个进程,仅当使用该插件时才创建),
- 3、GPU进程(最多一个,用于3D绘制),
- 4、浏览器渲染进程(内核,默认每一个tab页面都会新开一个进程,互不影响,控制页面渲染,脚本执行,事件处理等,某些情况下多个空白tab会合并一个进程)
每个tab页面可以看作是浏览器内核进程,然后这个进程是多线程的,它有几大类子线程:
- GUI线程
- JS引擎线程(是内核进程中的一个子线程,所以常说JS引擎是单线程的)
- 事件触发线程
- 定时器线程
- 网络请求线程
2、解析URL
输入URL后,就会进行解析(URL的本质就是统一资源定位符)
URL一般包括几大部分:
- protocol,协议头,譬如有http,ftp等
- host ,主机域名或IP地址
- port ,端口号
- path ,目录路径
- query ,即查询参数
- fragment ,即#键后面的hash值,一般用来定位到某个位置
每次网络请求时都需要开辟单独的线程进行,如果URL解析到http协议,就会新建一个网络线程去处理资源下载。
二、开启网络线程到发出一个完整的http请求(这一部分涉及到dns查询,tcp/ip请求,五层因特网协议栈等知识)
1、DNS查询得到IP
如果输入的是域名,需要进行dns解析成IP(dns解析很耗时,因此如果解析域名过多,会让首屏加载变得过慢,可以考虑dns-prefetch优化),大致流程:
- 如果浏览器有缓存,直接使用浏览器缓存,否则使用本机缓存,再没有的话就是用host;
- 如果本地没有,就像dns域名服务器查询(当然,中间可能还会经过路由,CDN调度器,也有缓存等),查询到对应的IP。
2、TCP/IP请求
http的本质就是tcp/ip请求,需要了解三次握手规则建立连接以及断开连接时的四次挥手。
tcp将http长报文划分为短报文,通过三次握手与服务器建立连接,进行可靠传输。三次握手的步骤(抽象派):
- 客户端:hello,你是server吗?
- 服务端:hello,我是server,你是client吗
- 客户端:yes,我是client
建立连接成功后,接下来就是正式传输数据。然后,待到断开连接时,因为是全双工的,所以需要进行四次挥手。四次挥手的步骤(抽象派): - 主动方:我已经关闭了向你那边的主动通道了,只能被动接收了;
- 被动方:收到通道关闭的信息;
- 被动方:那我也告诉你,我这边向你的主动通道也关闭了;
- 主动方:最后接收数据,之后双方无法通信
tcp/ip的并发限制:浏览器对同一域名下并发的tcp连接是有限制的(2-10个不等)。
get和post的区别:两者本质都是tcp/ip,除了在http层面的区别,在tcp/IP层面的规范层(不是对规范的实现)也有区别: - get会产生一个数据包,post会产生两个。(具体:get请求时,浏览器会把hesders和data一起发送出去,服务器响应200,返回数据; post请求时,浏览器先发送headers,服务器响应100 continue,浏览器再发送data,服务器响应200,返回数据。)
3、五层因特网协议栈
从客户端发出http请求到服务器接收,中间会经过一系列的流程(服务器的接收就是反过来的步骤):
- 在应用层发送http请求;(应用层:dns,http,dns解析成ip并发送http请求)
- 到传输层通过三次握手建立tcp/IP连接;(传输层:tcp,udp)
- 到网络层的IP寻址;(网络层:IP,ARP)
- 到数据链路层的封装成帧;(数据链路层:ppp)
- 到物理层的利用物理介质传输;
完整的OSI七层框架,多了会话层和表示层: 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。 - 表示层:主要处理两个通信系统中交换信息的表示方式,包括数据格式转换,数据加密与解密等
- 会话层:它具体管理不同用户和进程之间的对话,如控制登录和注销过程。
三、从服务器接收到的请求到对应后台接收到的请求(这一部分可能涉及到负载均衡,安全拦截以及后台内部的处理等等)
服务端在接收到请求时,内部会进行很多的处理:
1、负载均衡
对于大型的项目,由于并发访问量很大,所以往往一台服务器是吃不消的,一般会有若干台服务器组成一个集群,然后配合反向代理实现负载均衡。
简单地说,用户发起的请求都指向调度服务器(反向代理服务器,譬如安装了nginx控制负载均衡),然后调度服务器根据实际的调度算法,分配不同的请求给对应集群中的服务器执行,然后调度器等待实际服务器的http响应,并将它反馈给用户。
2、后台的处理
一般后台都是部署到容器中的,所以一般为:
- 先是容器接受到请求(如Tomcat容器)
- 然后对应容器中的后台程序接收到请求(如Java程序)
- 然后后台统一处理,处理完后响应响应结果
概括下:
- 一般的后端有统一验证,如安全拦截、跨域验证;
- 如上一步不符合规则,则直接返回相应的http报文(如拒绝请求等);
- 验证通过后,进入实际的后台代码,此时是程序接收到请求,然后执行(譬如查询数据,大量计算等等);
- 程序执行完毕后,返回一个http响应包(一般这一步也会经过多层封装);
- 然后就是讲这个包从后端发送到前端,完成交互。
四、后台和前台的http交互(这一部分包括http头部、响应吗、报文结构、cookie等知识、可以提下静态资源的cookie优化,以及编码解码,如gzip压缩等)
1、http报文结构
前后端交互时,http报文作为信息的载体。报文一本包括了:通用头部、请求/响应头部、请求/响应体。
2、cookie以及优化
cookie是浏览器的一种本地存储方式,一般用来帮助客户端和服务端通信,常用来进行身份校验,结合服务端的session使用。cookie常用场景如下(简述):
- 在登录页面,用户登录了
- 此时,服务端会生成一个session,session中有用户的信息(如用户名、密码等)
- 然后会有一个sessionid(相当于是服务器端的这个session对应的key)
- 然后服务端在登录页面中写入cookie,值就是:jssessionid = xxx
- 然后浏览器本地就有了这个cookie,以后访问同域名下的页面时,自动带上cookie,自动检验,在有效时间内无需二次登录。
一般来说,cookie是不允许存放敏感信息的(千万不要明文存储用户名、密码),因为非常不安全,如果一定要强行存储,首先,一定要在cookie中设置httponly(这样就无法通过js操作了),另外可以考虑rsa等非对称加密(因为实际上,浏览器本地是非常容易被攻克的,并不安全)。
另外,由于在同域名的资源请求时,浏览器会默认带上本地的cookie,正对这种情况,在某些场景中是需要优化的,例如页面中需要请求多个静态资源时,每次请求都带上cookie是很浪费的,因为静态资源不需要cookie验证,这时候可以采用域名拆分的方法,将静态资源分组,分别放到不同的子域名下,而子域名请求时,是不会带上父级域名cookie的,这样就避免了浪费。
3、长连接与短连接
首先看tcp/ip层面的含义:
长连接:一个tcp/ip连接上可以连续发送多个数据包,在tcp连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接,一般需要自己做在线维持(类似于心跳包)。
短连接:通信双方有数据交互时,就建立一个tcp连接,数据发送完成后,就断开此连接。
然后在http层面:http1.0中默认使用的短连接;http1.1中默认使用长连接(在服务器中可配置连接的持续时间)。
4、http2.0
http2.0不是https,它相当于是http的下一代规范。特性简述:
- 多路复用(一个tcp/ip连接可以请求多个资源)
- 首部压缩(http头部压缩,减少体积)
- 二进制分帧(在应用层与传送层之间增加了一个二进制分帧层,改进传输性能,实现低延迟和高吞吐量)
- 服务器端推送(服务端可以对客户端的一个请求发出多个响应,可以主动通知客户端)
- 请求优先级(如果流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定需要多少资源来处理该请求)
5、https
https就是安全版本的http,譬如一些支付操作都是基于https的,因为http请求的安全系数指数太低了。
简单来说,https与http的区别就是:在请求时,会建立ssl链接,确保接下来的通信都是加密的,无法被轻易截取分析。
一般来说,要将网站升级成https,需要后端的支持(后端要申请证书等)。
一般来说,主要关注的就是SSL/TLS的握手流程,如下简述:
a、浏览器请求建立SSL链接,并向服务端发送一个随机数-client random 和客户端支持的加密方法,比如RSA加密,此时是明文传输。
b、服务端从中选出一组加密算法和Hash算法,回复一个随机数-Server random,并将自己的身份信息以证书的形式发回给浏览器(证书里包含了网站地址,非对称加密公钥,以及证书颁发机构等信息)
c、浏览器收到服务端的证书后:
- 验证证书的合法性,如果证书信任,则浏览器会显示一个小锁头,否则会有提示
- 用户接收证书后(不管信不信任),浏览器会产生新的随机数-Premaster secret,然后证书中的公钥以及指定的加密方法加密Premaster secret,发送给服务器
- 利用Client random、Server random和Premaster secret通过一定的算法生成http链接数据传输的对称加密key-session key
- 使用约定好的HASH算法计算握手消息,并使用生成的session key对消息进行加密,最后将之前生成的所有信息发送给服务端。
d、服务端收到浏览器的回复
- 利用已知的加解密方式与自己的私钥进行解密,获取Premaster secret。
- 和浏览器相同规则生成session key
- 使用session key解密浏览器发来的握手消息,并验证Hash是否与浏览器发来的一致
- 使用session key加密一段握手消息发送给浏览器
e、浏览器解密并计算握手消息的Hash值,如果与服务端发来的Hash值一致,此时握手过程结束。
之后所有的https通信数据将由之前浏览器生成的session key并利用对称加密算法进行加密。
五、单独拎出来的缓存问题,http的缓存(这部分包括http缓存头部,etag,catch-control等)
前后端的交互中,使用缓存能很大限度上的提升效率,而且基本上对性能有要求的前端项目都是必用缓存的。
1、强缓存与弱缓存
缓存可以简单地划分为两种类型:强缓存(200 from cache)与协商缓存(304)。区别简述如下:
- 强缓存时,浏览器如果判断本地缓存未过期就直接使用,无需发起http请求。
- 协商缓存时,浏览器会向服务端发起http请求,然后服务端告诉浏览器文件未改变,让浏览器使用本地缓存。
对于协商缓存,使用Ctrl+F5强制刷新可以使得缓存无效。但是对于强缓存,在未过期时,必须更新资源路径才能发起新的请求(更改了路径相当于是另一个资源了,这也是前端中常用的技巧)。
2、缓存头部简述
上述提到了强缓存和弱缓存,那他们时怎么区分的呢?答案是通过不同的http头部控制。
- 属于强缓存控制的头部:(http1.1)Cache-Control/Max-Age (Max-Age是Cache-Control的值)
- (http1.0)Pragma/Expires
- 属于协商缓存控制的头部:(http1.1)If-None-Match/E-tag
- (http1.0) If-Modified-Since/Last-Modified (不同的头部属于不同的时期)
另外,HTML页面的meta标签可以控制缓存方案-Pragma 。 <META http-equiv="Pragma" content="no-cache">。但是该方案支持情况不佳(例如,缓存代理服务器肯定不支持),所以不推荐。
六、浏览器接收到http数据包后的解析流程(解析HTML词法,然后解析成dom树、解析css生成css规则树、合并render树,然后layout、painting渲染、复合图层的合成、GPU绘制、外链资源的处理、loaded和domcontentloaded等)
1、流程简述
浏览器内核拿到内容后,渲染步骤大致可以分为以下几步:
- 解析HTML,构建DOM树
- 解析CSS,生成CSS规则树
- 合并DOM树和CSS规则树,生成render树
- 布局render树(layout/reflow),负责各元素尺寸、位置的计算
- 绘制render树(paint),绘制页面像素信息
- 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。
如下图:
2、HTML解析,构建DOM
解析HTML到构建出DOM的过程可减简述如下: Bytes -> characters -> tokens ->nodes ->DOM
譬如假设有这样一个HTML页面:
<html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> <title>Critical Path</title> </head> <body> <p>Hello <span>web performance</span>students!</p> <div><img src="awesome-photo.jpg"></div> </body> </html>浏览器的处理如下:
列举其中的一些重点过程:
- Conversion转换:浏览器将获得的HTML内容(Bytes)基于他的编码转换为单个字符
- Tokenizing分词:浏览器按照HTML规范标准将这些字符转换为不同的标记token。每个token都有自己独特的含义和规则集
- Lexing词法分析:分词的结果是得到一堆的token,此时把他们转换为对象,这些对象分别定义他们的属性和规则
- DOM构建:因为HTML标记定义的就是不同标签之间的关系,这个关系就像是一个树形结构一样,例如:body对象的父节点就是HTML对象,然后p对象的父节点就是body对象
最后的DOM树如下:
3、生成CSS规则
CSS规则树的生成过程类似:Bytes ->characters ->token ->nodes ->CSSOM
譬如style.css内容如下:
body{font-size:16px} p{font-weight:bold} span{color:red} p span{display:none} img{float:right}
那么最终的CSSOM树就是:
4、构建渲染树
一般来说,渲染树与DOM树是相对应的,但不是严格意义上的一一对应。因为有一些不可见的DOM元素不会插入到渲染树中,如head这种不可见的标签或者display:none等。
整体看图:
5、渲染
渲染基本流程如下:
图中重要的四个步骤就是:
- 计算CSS样式
- 构建渲染树
- 布局,主要定位坐标和大小,是否换行,各种position overflow z-index属性
- 绘制,将图像绘制出来
然后,图中的线与箭头表示通过js动态修改了DOM和CSS,导致了重新布局(layout)或渲染(Repaint)。
这里,layout和Repaint的概念是有区别的:
- layout:也称为Reflow,即回流。一般以为这元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树。
- Repaint:即重绘。意味着元素发生的改变只是影响了元素的一些外观之类的s时候(例如:背景色,文字颜色等),此时只需要重新应用新样式绘制这个元素就可以了。
回流的成本开销要高于重绘,而且一个节点的回流往往会导致子节点以及同级节点的回流,所以优化方案中一般都包括,尽量避免回流。
什么会引起回流?
- 页面渲染初始化
- DOM结构改变,比如删除了某个节点
- render树变化,比如减少了padding
- 窗口resize
- 最复杂的一种:获取某些属性,引发回流
回流一定伴随着重绘,重绘却可以单独出现。所以一般会有一些优化方案: - 减少逐项更改样式,最好一次性更改style,或者将样式定义为class并一次性更新
- 避免循环操作dom,创建一个documentFragment或div,在它上面应用所以的DOM操作,最好再把它添加到window.document。
- 避免多次读取offset等属性,无法避免则将他们缓存到变量。
- 将复杂的元素绝对定位或固定定位,使得他们脱离文档流,否则回流代价会更高。
注意:改变字体大小会引发回流。
6、简单层与复合层
上述中的渲染止步于绘制,绘制可以结合复合层和简单层的概念来讲,简单介绍如下:
- 可以认为默认只有一个复合图层,所有的DOM节点都是在这个复合图层下的
- 如果开启了硬件加速功能,可以将某个节点变成复合图层
- 复合图层之间的绘制互不干扰,由GPU直接绘制
- 而简单图层中,就算是absolute等布局,变化时不影响整体的回流,但是由于在同一个图层中,仍然是会影响绘制的,因此,做动画时仍然性能很低,所以一般做动画推荐使用硬件加速
7、Chrome中的调试
Chrome的开发者工具中,Performance中可以看到详细的渲染过程:
8、资源外链的下载
在解析html时,会遇到一些资源连接,此时就需要进行单独处理,这里将遇到的静态资源分为几大类(未列举所有): css样式资源、JS脚本资源、img图片类资源。
遇到外链时的处理:当遇到上述外链时,会单独开启一个下载线程去下载资源(http1.1是每一个资源的下载都要开启一个http请求,对应一个tcp/ip链接)
遇到CSS样式资源(特点):
- CSS下载时异步,不会阻塞浏览器构建DOM树
- 但是会阻塞渲染,也就是在构建render树时,会等到css下载解析完毕后才进行(这点与浏览器优化有关,防止css规则不断改变,避免了重复的构建)。有例外,media query声明的CSS是不会阻塞渲染的。
遇到JS脚本资源(特点):
- 阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析html;
- 浏览器的优化,在脚本阻塞时,也会继续下载其他资源(当然有并发上限),但是虽然脚本可以并行下载,解析过程仍然是阻塞的,也就是说必须这个脚本执行完毕后才会接下来的解析,并行下载只是一种优化而已。
- defer(延迟执行)与async(异步执行),普通的脚本是会阻塞浏览器解析的,但是可以加上defer和async属性,这样脚本就变成异步了,可以等到解析完成后再执行。
遇到img图片类资源:
遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有src的地方。
七、css的可视化格式模型(元素的渲染规则,如包含块、控制框、BFC(块级框)、IFC(行内框)等概念)
css的可视化格式模型就是规定了浏览器在页面中如何处理文档树。
css的三种定位机制:普通流,浮动,绝对定位。
八、JS引擎解析过程(JS的解释阶段,预处理阶段,执行阶段生成执行上下文,VO,作用域链,回收机制等等)
JS脚本的执行需要引擎解析,主干流程如下:
1、JS的解析阶段:
首先得明确,JS是解析性语言,所以它无需提前编译,由解释器实时运行。
引擎对JS的处理过程可简述如下(核心:JIT编译器将源码编译成机器码运行):
读取代码,进行词法分析(Lexical analysis),然后将代码分解成词元(token);
对词元进行语法分析(parsing),然后将代码整成语法树(synntax tree);
使用翻译器(translator),将代码转为字节码(bytecode);
使用字节码解释器(bytecode interpreter),将字节码转为机器码。
2、 JS的预处理阶段:在正式执行JS前,会涉及函数提升,变量提升,分号补全等操作。
3、JS的执行阶段:
涉及概念:执行上下文,执行堆栈概念; VO(变量对象)和AO(活动对象); 作用域链; this机制等。
执行上下文简单解释:
- JS有执行上下文;
- 浏览器首次载入脚本,它将创建全局执行上下文,并压入执行栈栈顶(不可被弹出);
- 然后没进入其它作用域就创建对应的执行上下文并把它压入执行栈的顶部
- 一旦对应的上下文执行完毕,就从栈顶弹出,并将上下文控制权交给当前的栈
- 这样依次执行,最终都会回到全局执行上下文。
每一个执行上下文,都有三个重要属性:变量对象(VO,存放一些变量信息)、作用域链、this(不是某个变量对象的属性)。其中,只有全局上下文的变量对象允许通过VO的属性名称来间接访问(因为在全局上下文里,全局对象自身就是变量对象)。
AO,当函数被调用者激活,AO就被创建了。
可以理解为:在函数上下文中,VO === AO
在全局上下文中,VO === this ===global
4、回收机制
JS引擎的基础GC方案是:标记清除。
GC缺陷:GC时,停止响应其他操作。
优化方案:分代回收——多回收“临时对象”区;少回收“持久对象”区;减少每次需遍历的对象,从而减少每次GC的耗时。
九、其它(可以拓展不同的只是模块,如跨域,web安全,hybrid模式等等内容)
1、跨域(ajax)
2、web安全(ajax请求的安全性等)