[TOC]
必备-2.浏览器底层渲染机制全套详解
一、如何创建一个个人的网站
我们如何发布一个自己的网站,可以供别人看?
-
第一步:购买一台服务器:阿里云、腾讯云
- 每台服务器都有一个自己的外网IP:123.111.222.12
-
第二步:基于FTP上传工具[==fileZilla==]将自己创作的网站代码,放到服务器的磁盘空间中(==服务器本质就是一台电脑==)
-
第三步:基于nainx、apache、iis等把作品进行发布:在服务器创建一个web服务,让其管理项目资源(==一台服务器可以有多个服务==)
- 到目前为止:别人基于IP地址+端口号 就可以访问到我们的内容了,(==但是这样访问太麻烦了==)
-
第四步:这时就需要购买域名:域名的作用就是简化服务器web服务的访问方式
-
无域名访问:ip地址+端口号
-
有域名访问:www.baidu.com
-
购买到域名后,通过DNS解析,==使域名与服务器外网IP连接起来==,把解析记录放到DNS服务器中。
-
最后,到工信部备案
-
如何以开一家超市的例子来理解发布一个网站的全过程?
-
举例:我们如果想开一家超市需要有几个前提条件:
-
超市地址->货物->货物类型分区(蔬菜、家电、玩具...)->各专区服务员->取一个超市名->将超市名做成招牌,安到超市门口->到工信部注册,办营业许可证
- 这其中超市的创建步骤,其实与服务器创建的步骤可以一一对应的:
-
超市地址:服务器外网IP
-
货物:自己写的项目
-
货物类型分区:多个项目,每一个项目都是一个分区
-
各专区服务员:端口号
-
超市名:域名->www.baidu.com
-
超市名做成招牌,安门口:DNS解析,外网IP连接域名
-
营业许可证:到工信部注册
-
- 这其中超市的创建步骤,其实与服务器创建的步骤可以一一对应的:
二、URL请求网址过程
从输入URL地址到看到页面中间都经历了啥?
==一共分七步==
- 第一步:URL地址解析
- 第二步:缓存检查
- 第三步:DNS解析
- 第四步:TCP三次握手(==目的:建立客户端与服务器端的通道==)(修路)
- 第五步:基于HTTP或HTTPS等实现通信
- 第六步:TCP四次挥手
- 第七步:浏览器渲染获取的代码
1.URL地址解析
URL地址:http://www.xxx.com:80/index.html?name=sjh&age=18#video;
- 1、==http:传输协议==:[作用:用户客户端和服务器端的信息传输]
- http:超文本传输协议[最常用]
- https:http+SSL[比http更安全的传输机制]
- ftp:文件传输协议,把本地开发的资源上传到服务器
- 2、==www.xxx.com域名:==[作用:给外网IP起一个好听的名字]
- 顶级域名:xxx.com[需要购买]
- 一级域名:www.qq.com
- 二级域名:sports.qq.com
- 三级域名:kbs.sports.qq.com
- 3、==80端口号:==[作用:区分相同服务器上的不同服务的 0~65535]
- 浏览器有一个默认操作,如果我们自己没有写端口号,浏览器会根据传输协议自己加上默认的端口号
- http->80
- https->443:www.baidu.com->https://www.baidu.…
- ftp->21
- 浏览器有一个默认操作,如果我们自己没有写端口号,浏览器会根据传输协议自己加上默认的端口号
- 4、==index.html:请求资源的路径名称==[如果不写,则一般默认都是index.html(服务器可以自己指定默认路径]
- 5、==问号传参==:
- 把信息传递给服务器
- 在页面跳转的过程中,基于问号参数传递给另外一个页面一些信息
- ...
- 6、==#video哈希值==:
- 锚点跳转
- HASH路由
2.缓存检查
- 缓存方式有三种:==强缓存、协商缓存、数据缓存==
- 缓存位置有两种:
- 内存条:[Memory Cache]虚拟内存缓存(==临时==)
- 硬盘:[Disk Cache]物理内存缓存(==持久==)
- 缓存存取方式三种:最开始从服务器获取信息,如果存在缓存机制,则会在==Memory==和==Disk==各存储一份
- 普通刷新(F5):从Memory Cache 中获取
- 页面关闭再重新打开:从Disk Cache中获取
- 强制刷新(Ctrl+F5):清除本地缓存,重新从服务器获取最新的内容
2.1强缓存
强缓存:本地存在缓存(且未过期),则直接从本地缓存中获取渲染{==不论服务器端是否更新了内容==},如果没有缓存(或者过期了),才从服务器重新获取;
-
实现步骤:
- 第一次向服务器发送请求(==本地无缓存==):服务器把信息返回给客户端[同时在响应头中设置
Expires或者Cache-Control]:用来规定是否设置强缓存以及缓存的周期;客户端获取信息后,渲染的同时,根据缓存的规则,把信息缓存在本地;Cache-Control:max-age=2592000 单位秒 Http1.1expries: 具体时间 Http1.0- 两个都被服务器返回,则按照浏览器HTTP版本的支持度,去最高版本支持的项,http1.1比http1.0高
- 第二次向服务器发送请求(==本地有缓存==):先看本地是否有缓存以及是否过期,有且未过期,直接读取缓存的信息,如果缓存失效,则从服务器重新获取...,重新获取后再把最新的内容缓存到本地
- 第一次向服务器发送请求(==本地无缓存==):服务器把信息返回给客户端[同时在响应头中设置
-
如何设置强缓存:是由服务器端在响应头中基于Expires和Cache-Control设置缓存规则的,二客户端自己会根据这些规则,完成强缓存的存储及校验。
-
强缓存的作用:保证第二次及以后访问资源更快,对第一次没啥特殊效果!
-
强缓存的副作用:在缓存有效期内,服务器资源更新了,我们获取的也是本地缓存信息,无法获取最新的信息。==所以真实项目中,HTML页面是"绝对不能设置强缓存的",一旦html都缓存了,则和服务器彻底失联了,服务器不论如何更新,客户在缓存有效期内都无法获取最新的资源。==
- 加载一个页面,首先获取的是html,在浏览器渲染html的时候,遇到link/script/img等标签,才会从服务器获取这些资源信息。
- 解决方法:==html文件不做缓存==,服务器端只要代码更改,我们让其在html中重新导入新的文件名[使用webpack实现],我们下次再访问html页面时,新导入的文件会重新向服务器请求
-
强缓存特点:不论是从服务器获取的还是从缓存中读取的资源,返回状态码都是200。
2.2协商缓存
- 协商缓存:如果强缓存和协商缓存都设置了,必须等待强缓存失效后,才会执行协商缓存(协商缓存是对强缓存的一种辅助)
- 实现步骤:
- 第一次向服务器发送请求:服务器返回对应的资源信息[同时在响应头中返回两个字段]:
last-modified(HTTP 1.0):当前资源最后一次更新时间etag(HTTP1.1):只要服务器资源更新就会产生一个唯一的etag值- 当客户端获取信息之后,发现存在这两个字段会在客户端本地存储资源信息及相关表示字段
- 第二次发送请求:即便发现本地有缓存信息,也需要先和服务器协商【协商内容:问一下服务器,这个资源有没有再更新过?】
- 客户端把自之前缓存中的
last-modified和etag基于请求头中的If-Modified-Since和If-Node-Match发送给服务器,服务器根据传递过来的文件更新时间(标识)和目前服务器最新的更新时间(标识)去对比 - ==两次时间(标识)一致==:说明服务器资源距离上一次缓存没有任何的更新,此时服务器返回304状态码即可,客户端接收到这个状态后,从本地缓的信息中获取渲染即可
- ==如果不一致==:说明更新过,返回状态码==200==以及最新的信息(和新的last-modified和etag),客户端再重新渲染并缓存最新的信息
- 客户端把自之前缓存中的
- 第一次向服务器发送请求:服务器返回对应的资源信息[同时在响应头中返回两个字段]:
- 协商缓存也是由服务器设置的:在响应头中返回
last-modified和etag - 协商缓存是对于强缓存的一种补充:对于html页面无法设置缓存但是可以设置协商缓存、而且在强缓存失效的情况下,我们依然可以基于协商缓存验证一下服务器是否更新
- 实现步骤:
-------------------------》不论强缓存还是协商缓存,都是服务器设置的[在响应头返回对应的字段],而客户端需要做的事情,浏览器自己就处理了,不需要我们前端写啥代码:而且都是针对==于静态资源文件==的缓存设置(例如:html、css/js/图片...),对于ajax数据请求,强缓存和协商缓存是无效的。
2.3数据缓存
- 数据缓存:[ajax数据通信的时候,对于不经常更新的数据,我们基于本地存储实现缓存]
- 实现步骤:
- 第一次本地没有存储过任何信息:此时向服务发送ajax请求,从服务器获取数据,获取数据后一方面进行数据绑定和渲染,另外一方面把数据存储到本地【基于本地存储方案、设置有效时间】
- 后期再发送请求:首先看本地是否存储过这些数据,且是否过期
- ==存储过且未过期==:不需要从服务器获取,只需要获取本地存储的内容即可
- ==未存储或者过期了==:重新向服务器发送请求获取最新的信息,同时本地也把最新内容存储
- 数据缓存需要前端开发工程师基于 JS代码+本地存储方法,自己去实现
- 本地存储方案:把一些信息存储到本地(Application->Storage)应用->数据库
- 特点:明文存储而且可以被查看(所以重要敏感信息不要存储,即便存储也要加密)
- 而且存储的信息都是以字符串类型进行存储[==排除虚拟内存存储==]
- 有源的限制(在某个域下存储的信息,只能在本域中获取)
- 特点:明文存储而且可以被查看(所以重要敏感信息不要存储,即便存储也要加密)
- 实现步骤:
数据缓存存储的方案有哪些?
- @1 cookie:一段小文本信息(key-value格式)
- @2 localStore:本地数据库
- @3 sessionStore:会话数据库
- @4 vuex/redux:
- @5 IndexedDB:@5和@6都是本地数据库存储,==很不常用==
- @6 WebSQL:
数据缓存存储中,cookie、localStorage、sessionStorage、vuex/redux的区别?
- ==兼容性==:
- cookie:兼容所有浏览器
- localStorage/sessionStorage:H5新增的API,不兼容IE6~8
- ==存储内容大小:==
- cookie:同源下最多允许
4kb - localStorage/sessionStorage:同源下最多允许存储
5MB
- cookie:同源下最多允许
- ==有效期:==
- cookie:持久化存储,具备存储有效期,需要自己设置
- localStorage:持久化存储,除非手动删除,否则一直存着,不具备有效期
- sessionStorage:临时存储,页面刷新还在,关闭页面就没了
- vuex/redux:临时存储,页面刷新就没了
- ==稳定性==:
- cookie:不稳定,清除历史记录、电脑垃圾会清除cookie,网站无痕模式下禁止存放cookie
- localStorage/sessionStorage:稳定,干掉cookie的那些操作暂时都干不掉localeStorage/sessioStore
- ==与服务器关系==:
- cookie:无论服务器需不需要,每次请求,都会在请求头中携带cookie,影响请求速度
- localStorage/sessionStorage:绝对的本地存储,和服务器之间没有任何关系,可手动向服务器传缓存
数据缓存各存储方式详解?
- @1 cookie:
- document.cookie:获取所有Cookie
- document.cookie=(name,value):设置Cookie
- ==优点==:兼容所有的浏览器,是传统的本地存储方案
- ==缺点==:存储内容大小有限制,同源下最多允许==4kb==
- ==有效期==:持久化存储,具备存储有效期,需要自己设置
- ==稳定性==:不稳定,清除历史记录或者电脑垃圾可能会清除cookie、以及网站的隐私/无痕模式下是禁止存储cookie的
- ==与服务器的关系==:cookie不是单纯的本地存储,和服务器之间有"==猫腻==":知道本地存储cookie,无论服务器需不需要,每一次请求都会基于请求头中的cookie字段,把存储的信息传递给服务器(如果存储东西多,导致每一次请求会变慢);服务器只要在响应头中设置set-Cookie字段,浏览器自己就会在本地设置cookie;
- @2localStorage:
- localStorage.setItem([key],[value]):设置一个项
- localStorage.getItem([key]):获取一项
- localStorage.removeItem([key]):移除某项
- localStorage.clear():清除所有项
- ==缺点==:H5中新增的API,不兼容IE6~8
- ==优点==:存储内容有大小限制:同源下最多允许存储5MB
- ==有效期==:持久化存储,除非手动删除,否则一直存着,不具备有效期
- ==稳定性==:干掉cookie的那些操作暂时都干不掉localeStorage
- ==与服务器的关系:==绝对的本地存储,和服务器之间没有任何关系[可以==手动==把本地存储的信息传递给服务器]
- @3 sessionStorage:
- sessionStorage.setItem([key],[value]):设置一个项
- sessionStorage.getItem([key]):获取一项
- sessionStorage.removeItem([key]):移除某项
- sessionStorage.clear():清除所有项
- ==特点==:页面刷新还在,关闭页面就没了,无论是localStorage还是cookie,只要本地存储了信息,页面刷新或者页面关闭后重新打开,存储的信息都在,而sessionStore属于会话存储:页面刷新存储的信息时存在的,以为会话还没结束;但是一旦页面关闭,会话结束,则存储的信息就会释放掉。
- @4 vuex/redux:
- ==特点==:页面刷新就没了,他们类似于在全局上下文中创建全局变量(或者容器)来存储信息,所以只有页面刷新或者管理,之前存储的信息都会释放。
- @5 IndexedDB:@5和@6都是本地数据库存储,==很不常用==
- @6 WebSQL:
- ...
3.DNS解析
- 域名解析:基于域名到DNS服务器上进行查找,找到服务器的外网IP[每次解析大概需要==20~120==ms左右]
- 解析步骤:
- 1、递归查询:从本地获取DNS解析记录(本地解析记录一般是缓存下来的,也有自己手动配置的)
- ==递归查询的步骤==:客户端->浏览器的DNS解析缓存->本地HOSTS文件->本地DNS解析器缓存->本地DNS服务器
- 以上只要某一步找到,就返回给客户端,如果都没找到,再进行迭代查询
- 2、迭代查询:本地没有找到解析记录,才会去公网上查询
- ==迭代查询的步骤==:根域名服务器->顶级域名服务器->权威域名服务器
- 以上三步必须全部查询
- 1、递归查询:从本地获取DNS解析记录(本地解析记录一般是缓存下来的,也有自己手动配置的)
- 项目优化:
- 1、提高域名的解析效率/速度
- 把资源放到相同服务器的相同服务下,确保页面中域名解析的次数少,这样的操作虽然优化了DNS的解析,但是可能导致服务器压力过大,服务处理速度变慢,导致网站整体性能更差!(==所以真实项目中,我们宁愿增加域名解析次数,也会把不同的资源分散到不同的服务器上部署)==
- web服务器:处理html/css/js...静态资源
- 数据服务器:处理ajax请求的、实现数据管控和业务逻辑的
- 图片/音视频服务器:处理富媒体资源的
- ...
- 在域名解析增加的情况下,我们可以基于DNS Prefetch(DNS预解析)来优化域名
- 原理:利用link标签的异步处理,在GUI主线程渲染过程中,同时在预先完成DNS解析,这样后续到了指定资源请求的时候,DNS可能已经解析过了,此时我们从缓存中获取解析记录即可,也优化了页面渲染的速度!!
- 把资源放到相同服务器的相同服务下,确保页面中域名解析的次数少,这样的操作虽然优化了DNS的解析,但是可能导致服务器压力过大,服务处理速度变慢,导致网站整体性能更差!(==所以真实项目中,我们宁愿增加域名解析次数,也会把不同的资源分散到不同的服务器上部署)==
- 1、提高域名的解析效率/速度
4.TCP三次握手
-
TCP三次握手(==目的:建立客户端与服务器端的链接通道==)(修路)
- 通信协议:TCP[安全稳定]、UDP[快]
- ==TCP==:需要经历三次握手,才建立通道,虽然通道稳定,但是消耗时间
- ==UDP==:无需经历三次握手,直接传输,这样可能会"丢包",但是快...
- 通信协议:TCP[安全稳定]、UDP[快]
-
seq序号:用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标识 ack确认序号:只有ACK标志位为1时,确认序号字段才有效,ack=seq+1 标志位: ACK:确认序号有效 RST:重置连接 SYN:发起一个新连接 FIN:释放一个连接
5.基于HTTP建立连接
- ==http事务==:
- ==http报文==:请求报文&响应报文
- ==请求方式==:GET/POST/DELETE/PUT/HEAD/APTCH/OPTIONS
- ==HTTP状态码==:200 301 302 307 304 400 401 403 404 500 503 ...
- 请求主体&响应主体的数据格式要求...
6.TCP四次挥手
- :TCP四次挥手【==作用:把建立的链接通道释放掉==】
- 如果每一请求都需要重新的==TCP[三握四挥]==,很耽误时间也很消耗性能,我们最好保持TCP通道的持久性->"长链接"
- 长连接:服务器在响应头中设置
Connection:keep-alive,浏览器也在请求头中设置Connection:keep-alive; - 我们可以设定长连接的周期:
Keep-Alive:timeout=15,max=300;
- 长连接:服务器在响应头中设置
- 如果每一请求都需要重新的==TCP[三握四挥]==,很耽误时间也很消耗性能,我们最好保持TCP通道的持久性->"长链接"
7.浏览器底层渲染
GUI渲染的几大步骤?
- @1 ==生成DOM TREE==【描述DOM/节点的层级关系】
- @2 ==生成CSSOM TREE== 【等待样式资源获取到,GUI会进行渲染,把样式按照"层叠样式表"渲染为CSSOM TREE】
- @3 ==生成Render TREE==【把DOM TREE 和CSSOM TREE 结合在一起,规划每个节点应该渲染的样式。后期渲染页面就是按照这个来的】
- @3==Layout布局==【根据视口大小,计算每个节点在视口中的位置和大小等】
- 如果后期的某些操作会导致视口中的
节点位置,大小,或者视口大小发生改变,此时我们需要重新计算每个节点在视口中的最新位置等,我们把这个操作叫做“回流、重排(reflow)”;
- 如果后期的某些操作会导致视口中的
- @5 ==分层==【按照样式,分为不同的层级(文档流),并计算出每一层文档流中的节点如何渲染】
- @6 ==Painting绘制==【绘制完成的结果就是在浏览器中看到页面和效果】
- 如果后期某些节点的样式发生改变(位置和大小等不变,只是
样式、背景等样式改变),浏览器需要重新按照最新的样式去绘制,我们这个操作称为"重绘 Repaint"
- 如果后期某些节点的样式发生改变(位置和大小等不变,只是
- 页面第一次渲染一定会引发一次Layout布局和Painting布局
- 后期发生重排一定会引发重绘,但重绘不一定非要重排,重排非常的消耗性能(==这也是我们所谓操作DOM损耗性能的主要原因==)
线程与进程?
-
线程&进程:一个进程包含多个线程
- 进程:一个页面,应用程序,都是一个进程
- 线程:一个进程中有很多线程
-
浏览器是一个进程,进程中存在多种线程:
-
GUI渲染线程:自上而下渲染HTML/CSS代码
-
JS引擎线程:解析和渲染代码
-
HTTP网络线程:从服务器获取资源和数据
-
定时器的监听线程
-
事件监听线程
-
...
-
浏览器只会分配一个"JS引擎线程",js是单线程的
-
GUI渲染遇到不同类型标签的做法?
- 遇到link/img...:会开辟新的HTTP线程去获取资源,GUI继续向下执行【==异步==】
- 遇到:资源已经存在无需去服务器获取,但需要等待娶她外链资源获取回来后,GUI按照导入先后顺序依次渲染CSS样式,以保证样式的优先级!!【==同步==】
- 遇到@import:也会开辟新的HTTP线程去获取资源,但是会阻碍GUI的渲染,什么时候资源渠道,什么时候GUI才会继续向下执行【==同步==】
- 遇到:也是开辟新的HTTP线程去获取资源,阻止GUI的渲染,拿到js资源之后,交给
js引擎线程先渲染JS,等到js渲染完毕后,GUI再继续【==同步==】- 我们一般都把JS放到底部:作用:1、避免js妨碍GUI渲染 2、避免获取不到DOM
- 如果不放在底部,还想获取DOM:
- 办法一:基于DOMContentLoaded事件监听处理[本质:script的获取和渲染还是同步的,还会阻碍GUI,只不过我们把操作DOM的代码变成了异步的]
- 办法二:给script标签加async或者defer属性[本质:把script的获取和渲染改为异步的,不让其阻碍GUI的渲染]
- async:先开辟http线程异步获取,获取到数据后,立即阻碍GUI渲染,先执行jS代码。【==不关注JS依赖顺序==】
- defer:开辟http线程异步获取资源后,获取数据后,不阻碍GUI渲染,JS代码放到GUI渲染之后。【==关注JS依赖顺序==】