为什么要取名"前端复习318"?对的,没错,就是碰瓷大名鼎鼎的 此生必驾318 的318国道。 倒也不算是碰瓷,因为写这个复习文章就是为了找工作,上份工作辞职后做的最大胆的决定,就是自驾了318国道川西大环线。 沿途风景真是美的不像话,一直忘不掉啊!
CSS 垭口
1. 介绍下 flex 布局
flex 布局就是弹性布局,任何的元素都可以设置 flex 布局。通过设置属性 display:flex 来实现。
设置 flex 布局的元素就是一个 flex 容器,该元素下的所有子元素是容器里的项目。flex 布局有两个轴,主轴和交叉轴,默认水平方向是主轴,垂直方向是交叉轴。控制项目的排列方向。
flex 布局的属性有两种,一种是作用在容器上,一种是作用在项目上。
容器属性有6个:
- flex-direction 控制主轴的方向,纵和横
- flex-wrap 项目是否可以换行
- flex-flow 是 flex-direction 和 flex-wrap 的简写
- justify-content 控制项目在主轴上的对齐方式 5个属性值 flex-start flex-end center space-around space-between
- align-items 控制项目在交叉轴上的对齐方式 5个属性值 flex-start flex-end center stretch baseline
- align-content 控制项目在多根轴线上的对齐方式 , 这个必须在多行内容才起作用 7个属性值 flex-start flex-end center space-around space-between stretch
项目属性有6个 :
- order 项目的排列顺序 数字越小,越靠前 默认是0
- flex-grow 项目的放大比例 默认为0 即使存在剩余空间,也不放大
- flex-shrink 项目的缩小比例 默认是1 即空间不足,项目将缩小
- flex-basis 在分配多余空间之前,项目占据的主轴空间,默认值是auto,是项目的本身大小
- flex 是前面三个属性的简写 ,flex-grow,flex-shrink,flex-basis。默认是0 1 auto ,后两个属性可选。快捷值 none(0 0 auto)auto (1 1 auto) flex:1表示 1 1 0%
- align-self 单个项目可以设置不同于其他项目的对齐方式,可以覆盖align-items,属性值 auto flex-start flex-end center stretch baseline。
flex 赋值情况
- flex: none 相当于 flex: 0 0 auto;
- flex: auto 相当于 flex: 1 1 auto;
- flex: 非负数 x 相当于 flex: 非负数 x 1 0%;
- flex: 取值为一个长度或百分比 x 相当于 flex: 1 1 x;
- flex: 取两个非负数 x 和 y 相当于 flex: x y 0%;
- flex: 取值为一个非负数 x 和一个长度或百分比 y 相当于 flex: x 1 y;
2. BFC
warn: BFC 的范围包含创建该上下文元素的所有子元素,但不包括该 BFC 父元素内新 BFC 元素的内部元素。
也就是说:一个元素不能同时存在于两个 BFC 中
规则定义:
那 BFC 有什么用处呢?
1. 解决浮动元素令父元素高度陷塌的问题
解决方案:父元素触发BFC,形成一个独立的渲染区域,不受外部影响,也不影响外部布局
触发了BFC的元素在计算高度时,把浮动的子元素也算进去,变相的实现了清除内部浮动的目的
2. 解决margin垂直方向重合的问题(兄弟元素之间的外边距在垂直方向会取最大值而不是取和)
3. 两栏自适应布局,左侧固定右侧自适应的布局
****4. 个人观点,BFC 可能会减少重排影响范围 ****(限制在局部重排)
因为BFC内部的元素和外部的元素绝对不会互相影响,因此, 当BFC外部存在浮动时,它不应该影响BFC内部Box的布局,BFC会通过变窄,而不与浮动有重叠。同样的,当BFC内部有浮动时,为了不影响外部元素的布局,BFC计算高度时会包括浮动的高度。避免margin重叠也是这样的一个道理。
如何触发 BFC 布局?
- float 不为 none
- position 不为 relative 和 static
- overflow 为 auto hidden scroll
- display 的值为 table-cell 或 inline-block
3. 水平垂直居中
水平居中主要有:
flex , text-align , margin: auto
垂直居中主要有:
flex , line-height , margin-top / margin-bottom / calc 已知高度计算 , absolute 等
水平垂直居中:
参考这篇文章,我觉得写的很全了。☞ Here
tips:
为什么 absolute + top right bottom left 全为0 + margin auto 就能实现水平垂直居中?
关键点:
- 上下左右全为 0
- margin: auto
why? 当一个绝对定位元素,相对定位方向属性 (left 对 right , top 对 bottom) 同时有具体定位数值的时候,就会触发流体特性
流体特性:****块状水平元素,如div元素,在默认情况下(非浮动、绝对定位等),水平方向会自动填满外部的容器
具有流体特性绝对定位元素的margin: auto的效果和普通流体元素一模一样
- 如果一侧定值,一侧
auto,auto为剩余空间大小; - 如果两侧均是
auto, 则平分剩余空间;
4. 移动端 1px 如何实现
因为种种原因,1px 在PC端和移动端会有些不同,为什么会这样呢?我们要先了解下 px
CSS和浏览器都是使用 px 做为长度单位,称为css 像素。像素又分为以下几种
物理像素
是实际存在的物体,比如屏幕设备上最小的像素点。电脑屏幕分辨率为 1920 * 1080 ,就是指该屏幕宽有1920个像素点,长有1080个像素点。同样是1920 * 1080 分辨率的手机屏幕也是这个道理。但是电脑屏幕和手机屏幕上的 1像素 大小确是不一样的。物理像素是实际存在的实体,但是实际长度大小是可以不同的。
虚拟像素
css像素是其中一种
那为什么要分两种?
直接让CSS像素等于设备像素存在两个问题:
- 同样尺寸的屏幕,有不同的屏幕密度,显示效果差距太大
- 不同样尺寸的屏幕,有相同的屏幕密度,显示效果也会差距太大
举个栗子:
加入有两个一模一样屏幕大小的手机存在,手机A的分辨率 320 * 480,手机B的分辨率 640 * 960。理论上以物理像素来渲染,那一个元素在手机A上的看上去的大小就是手机B上的两倍。
况且现在手机分辨率各种各样,所以直接使用物理像素是不合适的。
设备像素比(Device Pixel Ratio,DPR)
一个设备的物理像素与逻辑像素之比
CSS像素 = 物理像素 / DPR
比如 iPhone6 的物理分辨率为 750 × 1334,DPR = 2
width = 750 / 2 = 375px;
height = 1334 / 2 = 667px
那如何实现移动端 1px 呢?我理解这个 1px 是指实现 1物理像素
1px实现方法:
1. 伪元素,配合scale缩放
.scale::after {
display: block;
content: '';
border-bottom: 1px solid #000;
transform: scaleY(.5);
}
2. meta 标签设置 viewport 缩放
简单的说,假如想设置1px的线,在dpr=2情况下,页面就缩放到原来的一半,此时就会正常显示1px的线;在dpr=3的情况下,页面就缩放到原来的三分之一,此时也能够正常显示1px的线。具体实现方式如下
const dpr = window.devicePixelRatio
const meta = document.createElement('meta') // 创建meta视口标签
meta.setAttribute('name', 'viewport') // 设置name为viewport
meta.setAttribute('content', `width=device-width, user-scalable=no, initial-scale=${1/dpr}, maximum-scale=${1/dpr}, minimum-scale=${1/dpr}`) // 动态初始缩放、最大缩放、最小缩放比例
3. box-shadow
5. 响应式布局
响应式布局指的就是,同一个网页页面能够兼容不同的设备,不同大小的屏幕有不同的布局,是为了解决移动互联网浏览问题。
常用方法:
1. 媒体查询
常用屏幕大小分割点
移动优先 min-width , PC 优先 max-width
2. 百分比布局
通过百分比单位,可以使得浏览器中组件的宽和高随着浏览器的高度的变化而变化,从而实现响应式的效果,非常方便强大。
缺点:
计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位。
width和height相对于父元素的width和height- 而
margin、padding不管垂直还是水平方向都相对比于父元素的宽度 border-radius则是相对于元素自身- 定位中
top和bottom则相对于直接非static定位(默认定位)的父元素的高度,left和right相对于父元素的宽度
这些计算基准的不同严重增加了使用百分比单位计算的复杂度。
3. REM
rem是CSS3新增的单位,并且移动端的支持度很高。rem单位都是相对于根元素 的font-size来决定大小的。所以当页面的大小发生变化时,只需要改变font-size的值,那么以rem为固定单位的元素的大小也会发生响应的变化。
4. 视口单位
vw , vh , vmin , vmax
5. 图片响应式
1. 使用 max-width (图片自适应)
img {
display: inline-block;
max-width: 100%;
height: auto;
}
inline-block 元素相对于它周围的内容以内联形式呈现,但与内联不同的是,这种情况下我们可以设置宽度和高度。 max-width保证了图片能够随着容器的进行等宽扩充(即保证所有图片最大显示为其自身的 100%。此时,如果包含图片的元素比图片固有宽度小,图片会缩放占满最大可用空间),而height为auto可以保证图片进行等比缩放而不至于失真。如果是背景图片的话要灵活运用background-size属性。
width:100%会导致它显示得跟它的容器一样宽。在容器比图片宽得多的情况下,图片会被无谓地拉伸。
2. 使用 srcset
<img srcset="photo_w350.jpg 1x, photo_w640.jpg 2x" src="photo_w350.jpg" alt="">
3. 使用 background-image
总结:
但每一种方式都是有缺点的,媒体查询需要选取主流设备宽度尺寸作为断点针对性写额外的样式进行适配,但这样做会比较麻烦,只能在选取的几个主流设备尺寸下呈现完美适配,另外用户体验也不友好,布局在响应断点范围内的分辨率下维持不变,而在响应断点切换的瞬间,布局带来断层式的切换变化,如同卡带的唱机般“咔咔咔”地一下又一下。
通过百分比来适配首先是计算麻烦,第二各个属性中如果使用百分比,其相对的元素的属性并不是唯一的,这样就造成我们使用百分比单位容易使布局问题变得复杂。通过采用rem单位的动态计算的弹性布局,则是需要在头部内嵌一段脚本来进行监听分辨率的变化来动态改变根元素字体大小,使得CSS与JS 耦合了在一起。通过利用纯css视口单位实现适配的页面,是既能解决响应式断层问题,又能解决脚本依赖的问题的,但是兼容性还没有完全能够接受。
在实际项目中,我们需要结合上面的方案才能实现合适的响应式布局。比如 rem 来做字体的适配,用 srcset 来做图片的响应式,宽度可以用
rem,flex,栅格系统等来实现响应式,然后可能还需要利用媒体查询来作为响应式布局的基础等等
-
设置viewport
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=..."> -
媒体查询
-
字体的适配(字体单位)
-
百分比布局
-
图片的适配(图片的响应式)
-
结合flex,grid,BFC,栅格系统等已经成型的方案
参考文章:简单粗暴的移动端适配方案-REM
6. animation, transform 和 transition常见属性
这些属性多上手使用,很快就能使用了。更重要的不是工具,而是思路。
7. CSS 实现宽度自适应 100%,宽度16:9的矩形
方法1:width: 100%, height:0, padding-bottom: 56.25%;
核心是计算 padding / margin 百分比是根据父元素宽度计算,本身宽度百分比等于父元素宽度。height:0 一定要写,内部元素或文字不占据高度了,padding不会被顶下来导致高度变大。
**方法2:**width: 100vw, height: 56.25vw;
**方法3:**width: 100%, overflow: auto;触发BFC
::after { content: " "; display: block; margin-top: 56.2%; }
用伪元素margin / padding撑起。这会被内部元素或文字顶下来,导致高度变大。配合绝对定位。
核心代码:
.box2 { overflow: hidden;}.box2-l { float: left; margin-bottom: -3000px; padding-bottom: 3000px;}.box2-r { margin-left: 200px;}
<span>margin和padding</span><div class="box box2"> <div class="box2-l">Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugiat alias odio voluptatem ex rerum sed quo assumenda, saepe ipsam laudantium vitae officia ducimus perspiciatis necessitatibus, id, atque porro amet. Alias.asdasd </div> <div class="box2-r"></div></div>
8. 实现左栏高度随右栏高度自适应
这个问题又叫做 等高布局
最基本的两栏用 flex 就可以了。
**方法2:**margin 负值与padding
左栏设置 margin-bottom: -xxxpx; padding-bottom: xxxpx; 父元素overflow: hidden; xxx 必须大于父元素最大高度即可。原理 margin-bottom 负值时基准线是border-bottom,会向上移动。具体看这篇文章 浅谈margin负值
浏览器与网络 垭口
1. 浏览器渲染原理及优化,重绘与重排
浏览器渲染原理及优化 参考这篇文章
浅析浏览器渲染原理
重绘重排 可以参考这几篇文章
网页性能管理详解 重排(reflow)和重绘(repaint) 字节前端提前批面试题:触发了几次回流几次重绘
总结概念:
浏览器渲染原理
浏览器得到资源渲染出一个页面主要分为五步:
- HTML代码转化成DOM
- CSS代码转化成CSSOM(CSS Object Model)
- 结合DOM和CSSOM,生成一棵渲染树(包含每个节点的视觉信息)
- 生成布局(layout),即将所有渲染树的所有节点进行平面合成
- 将布局绘制(paint)在屏幕上
这五步里面,第一步到第三步都非常快,耗时的是第四步和第五步。
"生成布局"(flow)和"绘制"(paint)这两步,合称为"渲染"(render)。
重排reflow 和重绘repaint
重排就是重新进行上述的第四步,重绘是重新进行上述的第五步。
"重绘"不一定需要"重排",****"重排"必然导致"重绘"
浏览器会尽量把所有的变动集中在一起,排成一个队列,然后一次性执行,尽量避免多次重新渲染。操作样式时要注意合理的写法。
提高性能方法:
1.DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。
2.不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式
3.尽量使用离线DOM,而不是真实的网面DOM,来改变元素样式
4.先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)
5.position属性为absolute或fixed的元素,重排的开销会比较小
6.使用 window.requestAnimationFrame()、window.requestIdleCallback()
浏览器渲染优化策略
为了更好的解决浏览器渲染阻塞,那我们一定要先知道什么问题会阻塞浏览器渲染啊。知己知彼,百战不殆。
在渲染树构建中,我们看到关键渲染路径要求我们同时具有 DOM 和 CSSOM 才能构建渲染树。所以说,**HTML 和 CSS 都是阻塞渲染的资源。**HTML 显然是必需的,但 CSS 的必要性可能就不太明显。(可以查看谷歌开发文档对此描述,链接在此)
默认情况下
- CSS 被视为阻塞渲染的资源。浏览器将阻塞渲染,直至 DOM 和 CSSOM 全都准备完成。所以说,CSS加载不会阻塞DOM树解析(异步加载时DOM照常构建),但会阻塞render树渲染。
- JavaScript 执行会“阻止解析器”:当浏览器遇到文档中的脚本时,它必须暂停 DOM 构建,将控制权移交给 JavaScript 运行时,让脚本执行完毕,然后再继续构建 DOM。
- 如果浏览器尚未完成 CSSOM 的下载和构建,浏览器将延迟脚本执行和 DOM 构建,直至其完成 CSSOM 的下载和构建
所以标签位置很重要,实际情况下可以遵循下面两个原则:
- CSS优先,先于JS资源。CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
- JS 应尽量少影响DOM构建,在最后引入。
异步 JS,设置 async 或 defer (必须有 src 属性才有作用,内联js无效)
绿色HTML解析,蓝色网络加载JS,红色执行JS
优化策略,谷歌的开发文档有介绍
- 使用 requestAnimationFrame 来实现视觉变化
- 降低复杂性或使用 Web Worker
- 拆分操作 DOM 的任务,分别在多个 frame 中完成
- 使用 Google 开发者工具分析性能
- 降低选择器复杂性
- 减少要计算样式的元素数量
- 尽可能避免布局操作
- 使用flexbox 而不是更早的布局模型
- 避免布局抖动
- 提升移动或淡出的元素。
- 降低和减少绘制区域,复杂性。
-
坚持使用 transform 和 opacity 属性更改来实现动画。
-
使用 will-change 或 translateZ 提升移动的元素。
-
避免过度使用提升规则;各层都需要内存和管理开销。
6. 对用户输入事件去抖动
- 避免长时间运行输入处理程序;它们可能阻止滚动。
- 不要在输入处理程序中进行样式更改。
- 使处理程序去除抖动;存储事件值并在下一个 requestAnimationFrame 回调中处理样式更改。
总结:
根据上面的方法,我们可以看到优化策略都是基于 CSS 和 JS 优化。
CSS 要避免复杂布局,降低样式复杂度。尽量减少重排和重绘的次数,如果无法避免,也要降低它们的影响范围。CSS3 动画尽量使用 transform 和 opacity,使用 translateZ 触发 GPU 加速。
JS 方面要优化 JS 执行,避免频繁操作 DOM。将读操作与写操作分开,有利于渲染。通过 defer 或 async 异步下载 JS 脚本。减少复杂性,使用 web worker。使用 requestAnimationFrame,对用户输入事件进行去防抖。
2. 浏览器缓存
HTTP 报文
浏览器缓存机制又叫做 HTTP 缓存机制。其机制是根据 HTTP 报文的缓存标识进行的,浏览器第一次请求某资源时,会将响应的资源和缓存标识一起缓存起来,之后在请求同一资源时会去浏览器中查找该资源,并且比对上次响应 response header 的缓存标识。
我们先简单了解下 HTTP 请求/响应报文,这是理论上的图片介绍。
我们以掘金首页为例,看下实际内容
HTTP 请求(Request)报文:请求行 – HTTP头(通用信息头,请求头,实体头) – 请求报文主体(只有POST才有报文主体)
HTTP 响应(Response)报文:状态行 – HTTP头(通用信息头,响应头,实体头) – 响应报文主体
详细字段就不仔细介绍了,有兴趣的同学可自行查阅。
缓存过程分析
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
以上两点结论就是浏览器缓存机制的关键,确保了每个请求的缓存存入与读取。
根据是否需要向服务器重新发起 HTTP 请求将缓存过程分为两个部分,分别是 **强制缓存 **和 协商缓存 。
强制缓存优先于协商缓存进行,若强制缓存生效则直接使用缓存,若不生效则进行协商缓存,协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存。
强缓存
**强制缓存 就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。**强制缓存的情况主要有三种:
- 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)。
- 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存。
- 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果。
强制缓存的缓存规则是什么?
控制强制缓存的字段分别是 Expires 和 Cache-Control,其中 Cache-Control 优先级比 Expires 高。
**Expires: **是 HTTP/1.0 控制网页缓存的字段,其值为该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。
Expires 最大的缺陷是会产生时间误差,其原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存则会直接失效,这样的话强制缓存的存在则毫无意义。到了 HTTP/1.1,Expire已经被Cache-Control替代。
**Cache-Control: **在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
- public:所有内容都将被缓存(客户端和代理服务器都可缓存)
- private:所有内容只有客户端可以缓存,Cache-Control的默认取值
- no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
- no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
- max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
-
协商缓存生效,服务器返回304,表示资源无更新,浏览器直接使用缓存内容。
-
协商缓存失效,浏览器发现内容更新了,重新返回请求结果和状态码 200
协商缓存的缓存规则是什么?
控制协商缓存的字段分别有:Last-Modified / If-Modified-Since 和 Etag / If-None-Match,其中 Etag / If-None-Match 的优先级比 Last-Modified / If-Modified-Since 高。
-
Last-Modified / If-Modified-Since
-
Last-Modified 是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。Last-Modified 需要保证服务器时间准确
-
If-Modified-Since 则是客户端再次发起该请求时,携带上次请求返回的 Last-Modified 值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器会将 If-Modified-Since 的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于 If-Modified-Since 的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件。
-
Etag / If-None-Match
-
Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)
-
If-None-Match 是客户端再次发起该请求时,携带上次请求返回的唯一标识 Etag 值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器会将 If-None-Match 的字段值与该资源在服务器的 Etag 值做对比,一致则返回 304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为 200。
优缺点
所以根据优缺点可以调整不同文件的缓存策略。比如,Etag 适合重要量小的资源,Last-Modify 适合不重要的量大的资源。
从上面各类缓存的优缺点可以看出,每一种缓存都不是完美的。建议像下面这样做:
-
不要缓存 HTML 文件,避免缓存后用户无法及时获取到更新内容。
-
使用`Cache-Control`和`ETag`来控制 HTML 中所使用的静态资源的缓存。一般是将`Cache-Control`的`max-age`设成一个比较大的值,然后用`ETag`进行验证。
-
使用签名或者版本来区分静态资源。这样静态资源会生成不同的资源访问链接,不会产生修改之后无法感知的情况。
-
对于频繁变动的资源,可以使用 `Cache-Control: no-cache` 并配合 `ETag` 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新。
-
对于代码文件来说,通常使用 `Cache-Control: max-age=31536000` 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件。
还有两个本文没有介绍的内容,但是不建议大家使用:
-
使用 HTML 的 meta 标签来指定缓存行为
-
使用查询字符串来避免缓存。因为缓存有一些[已知的问题](https://gtmetrix.com/remove-query-strings-from-static-resources.html),使用查询字符串会导致有些代理服务器不缓存资源。
缓存位置
内存缓存(memory cache) 和 硬盘缓存(disk cache)
-
内存缓存(memory cache):优点 快速读取和时效性:
-
快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
-
时效性:一旦该进程关闭,则该进程的内存则会清空。
-
硬盘缓存(disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
在浏览器中,浏览器会在 js 和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取;而 css 文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存。
参考文章: