介绍下缓存
缓存的内容:DNS缓存和页面资源缓存。
DNS缓存就是在浏览器本地把对应的IP和域名关联起来
页面缓存:分为强缓存和协商缓存,缓存机制相关的字段都是在请求和响应头上
强缓存
在缓存有效期内,客户端直接读取本地资源,强缓存返回的状态码是 200
Expires
在http1.0中使用,表示资源失效的具体时间点Expires:Sat, 09 Jun 2018 08:13:56 GMT ,若是服务器和本地时间不一致,可能就会出现问题。
Cache-Control
HTTP/1.1定义的Cache-Control头用来区分对缓存机制的支持情况,请求头和响应头都支持这个属性。通过它提供的不同的值来定义缓存策略。
max-age: 缓存的有效时间,单位秒max-age=30672000。针对应用中那些不会改变的文件,通常可以手动设置一定的时长以保证缓存有效,例如图片、css、js等静态资源。no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载(未修改会返回304)。no-store:禁止进行缓存。缓存中不得存储任何关于客户端请求和服务端响应的内容,每次由客户端发起的请求都会下载完整的响应内容。must-revalidate:缓存验证确认。缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。
协商缓存
协商缓存,关键在于协商,在使用本地缓存之前,需要先跟服务器做个对比,服务器告知你的资源可用,是最新的,那就可以直接取本地资源,反之,服务器返回最新的资源给客户端,客户端收到后更新本地资源。
状态码:
若本地资源是最新的,那么返回304
若比对后,需要从服务器获取最新资源,那就是正常的 200
Last-modified
采用资源最后修改时间来判断,单位精度秒
Last-Modified:服务器资源的最新更新时间Tue, 14 Jan 2020 09:18:29 GMTIf-Modified-Since:发起协商,把本地记录的文件更新时间传给服务器,服务器进行判断比较 这个判断方式是http1.0的产物,因为时间精度是秒,若文件的更新频率在秒级以内,就会出现文件不一致。
ETag
为了解决上面的那个问题,http1.1加了这组标记
ETag:服务器根据内容生成唯一的字符串标识If-None-Match:发起协商,把本地记录的hash标识传给服务器,服务器进行判断比较
浏览器缓存的不足
当服务器返回的响应中有Expires或者Cache-Control设置了max-age响应头的时候,浏览器不会向服务器发起校验请求,而是直接复用本地缓存。如果此时服务器进行了资源的更新,用户就无法获取到最新的资源,只能通过强制刷新浏览器缓存来跟服务器请求最新的资源。
此外,Expires是服务器返回的一个绝对时间,在服务器时间与客户端时间相差较大时,缓存管理容易出现问题,比如随意修改下客户端时间,就能影响缓存命中的结果。
CDN缓存
当服务接入了CDN之后,浏览器本地缓存的资源过期之后,浏览器不是直接向源服务器请求资源,而是转而向CDN边缘节点请求资源。CDN边缘节点中将用户的数据缓存起来,如果CDN中的缓存也过期了,CDN边缘节点会向源服务器发出回源请求,从而来获取最新资源。
从输入URL到页面展示过程

1. 用户输入
当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是搜索内容,还是请求的URL。如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的URL。如果判断输入内容符合URL规则,那么地址栏会根据规则,把这段内容加上协议,合成为完整的URL浏览器构建请求行信息(如下所示),构建好后,浏览器准备发起网络请求。
2. URL请求过程
浏览器进程会通过进程间通信(IPC)把URL请求发送至网络进程,网络进程接收到URL请求后,会在这里发起真正的URL 请求流程。
首先,网络进程会查找本地缓存是否缓存了该资源。如果有缓存资源,那么直接返回资源给浏览器进程;如果在缓存中没有查找到资源,那么直接进入网络请求流程。
2.1 DNS`解析
以获取请求域名的服务器IP地址。如果请求协议是HTTPS,那么还需要建立TLS连接。
2.2 利用ip地址和服务器建立tcp连接
2.3 构建请求信息
浏览器端会构建请求行、请求头等信息,并把和该域名相关的Cookie等数据附加到请求头中,然后向服务器发送构建的请求信息。
2.4 接收并解析响应内容
服务器响应后,网络进程接收响应头和响应信息,并解析响应内容
3. 网络进程解析响应流程
3.1 检查状态码
在接收到服务器返回的响应头后,网络进程开始解析响应头,如果发现返回的状态码是301或者302,那么说明服务器需要浏览器重定向到其他URL。这时网络进程会从响应头的Location字段里面读取重定向的地址,然后再发起新的HTTP或者HTTPS 请求。
3.2 检查响应类型Content-Type
浏览器会根据响应类型Content-Type来决定如何显示响应体的内容。
- 比如
Content-Type的值是application/octet-stream,表示显示数据是字节流类型的,通常情况下,浏览器会按照下载类型来处理该请求,那么该请求会被提交给浏览器的下载管理器,同时该URL请求的导航流程就此结束。 - 如果
Content-type字段的值是text/html,浏览器会继续进行导航流程。由于Chrome的页面渲染是运行在渲染进程中的,所以接下来就需要准备渲染进程了。
4. 准备渲染进程
通常情况下,打开新的页面都会使用单独的渲染进程,但如果从A页面打开B页面,且A和B都属于同一站点的话,那么B页面复用A页面的渲染进程,如果是其他情况,浏览器进程则会为B创建一个新的渲染进程。
4. 提交文档
渲染进程准备好之后,网络进程会和准备好的渲染进程建立传输数据的“管道”,然后将通过URL请求到的响应体数据传送给渲染进程。等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程。浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的URL、前进后退的历史状态,并更新Web页面。
到这里,一个完整的导航流程就“走”完了,这之后就要进入渲染阶段了。
5. 渲染阶段
一旦文档被提交,渲染进程便开始页面解析和子资源加载,一旦页面生成完成,渲染进程会发送一个消息给浏览器进程,浏览器接收到消息后,会停止标签图标上的加载动画。
浏览器渲染流程
页面渲染都在浏览器的渲染进程里完成,具体包括:构建DOM树,样式计算,布局阶段,分层,绘制,分块,光栅化和合成。
1. 构建DOM树
浏览器不能直接理解HTML数据,所以第一步需要HTML解析器将HTML文件解析成一棵DOM树。这个DOM树其实就是浏览器里的document对象,我们在页面的控制台打印这个document对象的时候,会发现他是一个大的对象,里面的结构和HTML文档中的结构基本一样我们也可以通过js对这个对象里面的dom节点进行操作。
2. 样式计算
生成DOM树后,还需要根据CSS样式表,来计算出DOM树所有节点的样式,这个阶段大体可分为三步来完成。
2.1 把CSS转换为浏览器能够理解的结构styleSheets
CSS样式来源主要有三种:
- 通过
link引用的外部CSS文件 <style>标记内的CSS- 元素的
style属性内嵌的CSS
和HTML文件一样,浏览器也是无法直接理解这些纯文本的CSS样式,所以当渲染引擎接收到CSS文本时,会执行一个转换操作,将CSS文本转换为浏览器可以理解的结构——styleSheets。在控制台中输入document.styleSheets,然后就看到styleSheets的结构。,该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础。
2.2 转换样式表中的属性值,使其标准化
上一步已经把现有的CSS文本转化为浏览器可以理解的结构了,但CSS文本中有很多属性值如2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值。
font-size: 2em -> 32pxfont-weight: bold -> 700color:red -> rgb(255,0,0)
2.3 计算出 DOM 树中每个节点的具体样式
现在样式的属性已被标准化了,接下来就需要计算DOM树中每个节点的样式属性了,节点属性计算主要就涉及到CSS的继承规则和层叠规则了。
继承规则:父元素设置一些属性, 子元素也可以使用,比如颜色,字体,字体大小等属性都可以被继承。层叠规则:层叠是CSS的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在CSS处于核心地位,CSS的全称“层叠样式表”正是强调了这一点。
总之,在计算过程中需要遵守CSS的继承和层叠两个规则。这个阶段最终输出的内容是每个DOM节点的样式,并被保存在 ComputedStyle的结构内。
3. 布局阶段
现在,我们有DOM树和DOM树中元素的样式,但这还不足以显示页面,因为我们还不知道DOM元素的几何位置信息。那么接下来就需要计算出DOM树中可见元素的几何位置,并将其保存在布局树中,我们把这个计算过程叫做布局。
3.1 创建布局树
DOM树还含有很多不可见的元素,比如head标签下的内容与使用了display:none属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树。
具体步骤是遍历DOM树中的所有可见节点,并把这些节点加到布局中,不可见的节点会被布局树忽略掉。
3.2. 布局计算
现在我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了。在执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容,
4. 分层
获得了布局树后,还不能立即进行页面绘制,因为页面中有很多复杂的效果,如一些复杂的3D变换、页面滚动,或者使用 z-indexing做z轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。但如果元素有了层叠上下文的属性或者需要被剪裁,满足这任意一点,就会被提升成为单独一层
5. 图层绘制
在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,绘制的结果是得到一张绘制列表。列表里是一组记录绘制步骤的指令,每个指令都是一个简单的绘制操作,比如绘制一个矩形或画一条线。
6. 栅格化
当图层的绘制列表准备好之后,主线程会把该绘制列表提交给合成线程

(viewport)。
在有些情况下,有的图层可以很大,比如有的页面使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。
基于这个原因,合成线程会将图层划分为图块,这些图块的大小通常是256x256或者512x512
然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。
通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图的过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中。GPU操作是运行在GPU进程中,如果栅格化操作使用了GPU,那么最终生成位图的操作是在GPU中完成的,这就涉及到了跨进程操作。渲染进程把生成图块的指令发送给GPU,然后在GPU中执行生成图块的位图,并保存在GPU的内存中。
7. 合成和显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令并将该命令提交给浏览器进程,浏览器进程根据指令内容将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面了。
重排 VS 重绘
重排
当渲染树Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
常见的会导致重排的操作:
- 页面渲染初始化(这个无法避免)
- 元素位置、尺寸、内容改变
- 查询某些属性或调用某些方法
- 添加或者删除可见的
DOM元素 - 浏览器窗口尺寸改变
重绘
当页面中元素的改变只是影响元素的外观,风格,而不会影响布局的,(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
如何避免
现代浏览器会对频繁的重排或重绘操作进行优化:浏览器会维护一个队列,把所有引起重排和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。
CSS
- 避免使用table布局。
- 尽可能在DOM树的最末端改变class。
- 避免设置多层内联样式。
- 将动画效果应用到position属性为absolute或fixed的元素上。
- 避免使用CSS表达式(例如:calc())。
JavaScript
- 避免频繁操作样式,最好一次性重写
style属性,或者将样式列表定义为class并一次性更改class属性。 - 避免频繁操作
DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。 - 也可以先为元素设置
display:none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
了解PWA吗
一个PWA应用首先是一个网页, 可以通过Web技术编写出一个网页应用. 随后添加上App Manifest 和Service Worker 来实现PWA 的安装和离线等功能,相较于web应用,PWA的主要特点在:
- 可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏
- 实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能
- 实现了消息推送
Service Worker是Chrome团队提出和力推的一个WEB API,用于给web应用提供高级的可持续的后台处理能力。Service Worker的理念主要是在页面和网络之间增加一个拦截器,用来拦截请求和缓存资源。

Service Worker有以下功能和特性:
- 一个独立的
worker线程,独立于当前网页进程,有自己独立的worker context - 一旦被
install,就永远存在,除非被手动unregister - 用到的时候可以直接唤醒,不用的时候自动睡眠
- 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)
- 离线内容开发者可控
- 能向客户端推送消息
- 不能直接操作
DOM - 必须在
HTTPS环境下才能工作 - 异步实现,内部大都是通过
Promise实现
什么是同一站点
跨域的解决方案
jsonp
JSONP的原理很简单,就是利用 <script>标签没有跨域限制的漏洞。通过<script>标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时,不过只限于get请求
- 原生实现
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
跨域资源共享(CORS)
CORS(Cross-Origin ResourceSharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。
CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。 目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
反向代理
跨域原理:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
Nodejs中间件代理跨域
Node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
js事件的几种绑定方式
- 在
dom元素中直接绑定,<div class="an" onclick="aa()">aaaa</div> js中绑定document.getElementById("demo").οnclick=function(){}- 添加监听事件
document.addEventListener('name',()=>{})
什么是事件委托
浏览器中的事件触发有三个阶段:
- 最从外层开始往里传播,即事件捕获阶段
- 事件抵达了目标节点,即目标阶段
- 从目标阶段往外层返回,即冒泡阶段
事件委托也叫事件代理,在dom节点中,因为有事件冒泡机制,所以子节点的事件可以被父节点捕获。因此,在适当的场景下将子节点的事件用父节点监听处理,支持的事件点击事件,鼠标事件监听。事件代理的优势:
- 可以减少监听器的数量,减少内存占用
- 对于动态新增的子节点,可以实现事件监听
关于事件捕获和事件冒泡的理解:
事件捕获:事件从外往里传播,addEventListener最后一个参数设置成true就可以捕获事件,默认是false。
事件冒泡:事件由内往外传播,冒泡是人类理解事件的思维。
target与currentTarget区别
target:指的是事件流的目标阶段,获取的是被点击的元素。
currentTarget:在事件流的捕获和冒泡阶段时,是指向当前事件活动对象,只有在目标阶段的时候,两者才会相等
CSS加载问题
根据页面渲染流程可得知:
- css加载不会阻塞DOM树的解析;
- css加载会阻塞DOM树的渲染;
- css加载会阻塞后面js语句的执行
介绍下资源预加载 prefetch/preload async/defer
prefetch preload
都是告知浏览器提前加载文件(图片、视频、js、css等),但执行上是有区别的。
prefetch:其利用浏览器空闲时间来下载或预取用户在不久的将来可能访问的文档。<link href="/js/xx.js" rel="prefetch">
preload: 可以指明哪些资源是在页面加载完成后即刻需要的,浏览器在主渲染机制介入前就进行预加载,这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。<link href="/js/xxx.js" rel="preload" as="script"> 需要 as 指定资源类型。
js async 和 defer的区别
用于js脚本预加载
async : 加载脚本和渲染后续文档元素并行进行,脚本加载完成后,暂停html解析,立即解析js脚本
defer : 加载脚本和渲染后续文档元素并行进行,但脚本的执行会等到 html 解析完成后执行
Web Worker
浏览器性能监控
前端安全防范
跨站脚本攻击(XSS)
黑客往用户页面注入恶意js脚本,来进行非法行为,比如窃取用户cookie信息、监听用户行为、修改dom、生成广告浮窗等。
- 存储型
首先黑客利用站点漏洞将一段恶意js代码提交到网站的数据库中;然后用户向网站请求包含了恶意js脚本的页面;当用户浏览该页面的时候,恶意脚本就会将用户的Cookie信息等数据上传到服务器。
- 反射型
恶意js脚本属于用户发送给网站请求中的一部分,随后网站又把恶意js脚本返回给用户。当恶意js脚本在用户页面中
被执行时,黑客就可以利用该脚本做一些恶意操作。
- 基于DOM的XSS攻击
在Web资源传输过程或者在用户使用页面的过程中修改Web页面的数据。
防范手段:
- 服务器对输入脚本进行过滤或转码
- 充分利用内容安全策略(
CSP),比如限制加载其他域下的资源文件、禁止向第三方域提交数据、禁止执行内联脚本和未授权的脚本。 - 使用
HttpOnly属性
跨站请求伪造(CSRF)
黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。
- 自动发起get或者post请求
- 引诱用户点击链接
防范手段
- 充分利用好
Cookie的SameSite属性,能实现从第三方站点发送请求时禁止Cookie的发送。Strict、Lax 和 None 三个值 - 在服务器端验证请求来源的站点,可结合
referrer和origin属性 - CSRF Token