为什么要取名"前端复习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
BFC(Block Formatting Context )块级格式化上下文,指的是一个独立的渲染区域,其内部元素的布局不会影响到外部元素布局,反之亦然。
warn: BFC 的范围包含创建该上下文元素的所有子元素,但不包括该 BFC 父元素内新 BFC 元素的内部元素。
也就是说:一个元素不能同时存在于两个 BFC 中
规则定义:
- 内部的Box会在垂直方向上一个接一个的放置
- 内部的Box垂直方向上的距离由margin决定。(属于同一个BFC的两个相邻Box的margin还是会发生折叠,不同BFC则不会。)
- 每个元素的左外边距与包含块的左边界相接触(从左向右),即使浮动元素也是如此。(这说明BFC中子元素不会超出他的包含块,而position为absolute的元素可以超出他的包含块边界)
- BFC的区域不会与float的元素区域重叠
- 计算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实现方法:
- 伪元素,配合scale缩放
.scale::after {
display: block;
content: '';
border-bottom: 1px solid #000;
transform: scaleY(.5);
}
- 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}`)
// 动态初始缩放、最大缩放、最小缩放比例
- box-shadow
参考链接
5. 响应式布局
响应式布局指的就是,同一个网页页面能够兼容不同的设备,不同大小的屏幕有不同的布局,是为了解决移动互联网浏览问题。
常用方法:
一、媒体查询
常用屏幕大小分割点
移动优先 min-width , PC 优先 max-width
二、百分比布局
通过百分比单位,可以使得浏览器中组件的宽和高随着浏览器的高度的变化而变化,从而实现响应式的效果,非常方便强大。
缺点:
计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位。
各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如
- width 和height相对于父元素的width和height
- 而margin、padding不管垂直还是水平方向都相对比于父元素的宽度
- border-radius则是相对于元素自身
- 定位中top和bottom则相对于直接非static定位(默认定位)的父元素的高度,left和right相对于父元素的宽度 这些计算基准的不同严重增加了使用百分比单位计算的复杂度。
三、REM
rem是CSS3新增的单位,并且移动端的支持度很高。rem单位都是相对于根元素 的font-size来决定大小的。所以当页面的大小发生变化时,只需要改变font-size的值,那么以rem为固定单位的元素的大小也会发生响应的变化。
四、视口单位
vw , vh , vmin , vmax
五、图片响应式
- 使用 max-width (图片自适应)
img {
display: inline-block;
max-width: 100%;
height: auto;
}
inline-block 元素相对于它周围的内容以内联形式呈现,但与内联不同的是,这种情况下我们可以设置宽度和高度。 max-width 保证了图片能够随着容器的进行等宽扩充(即保证所有图片最大显示为其自身的 100%。此时,如果包含图片的元素比图片固有宽度小,图片会缩放占满最大可用空间),而 height为auto 可以保证图片进行等比缩放而不至于失真。如果是背景图片的话要灵活运用 background-size 属性。
width:100% 会导致它显示得跟它的容器一样宽。在容器比图片宽得多的情况下,图片会被无谓地拉伸。
- 使用 srcset
<img srcset="photo_w350.jpg 1x, photo_w640.jpg 2x" src="photo_w350.jpg" alt="">
- 使用 background-image
总结:
响应式布局的实现可以通过 媒体查询 + px , 媒体查询 + 百分比,媒体查询 + rem + js, vm/vh, vm/vh + rem 还有 flex 布局,grid 布局等等,这些方式来实现。
但每一种方式都是有缺点的,媒体查询需要选取主流设备宽度尺寸作为断点针对性写额外的样式进行适配,但这样做会比较麻烦,只能在选取的几个主流设备尺寸下呈现完美适配,另外用户体验也不友好,布局在响应断点范围内的分辨率下维持不变,而在响应断点切换的瞬间,布局带来断层式的切换变化,如同卡带的唱机般“咔咔咔”地一下又一下。
通过百分比来适配首先是计算麻烦,第二各个属性中如果使用百分比,其相对的元素的属性并不是唯一的,这样就造成我们使用百分比单位容易使布局问题变得复杂。通过采用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常见属性
这些属性多上手使用,很快就能使用了。更重要的不是工具,而是思路。
动画 animation
2D / 3D 转换 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. 实现左栏高度随右栏高度自适应
这个问题又叫做 等高布局
方法1:flex 布局
最基本的两栏用 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,生成 render 树(包含每个节点的视觉信息)
-
布局 render 树(Layout/reflow),负责各元素尺寸、位置的计算
-
绘制 render树(paint),将各个节点绘制到屏幕上。绘制可以将布局树中的元素分解为多个层。将内容提升到GPU上的层(而不是CPU上的主线程)可以提高绘制和重新绘制性能。
-
浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。当文档的各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容。
这五步里面,第一步到第三步都非常快,耗时的是第四步和第五步。
生成布局(flow) 和 绘制(paint) 这两步,合称为 渲染(render)。
二、重排reflow 和重绘repaint
重排就是重新进行上述的第四步,重绘是重新进行上述的第五步。
"重绘"不一定需要"重排","重排"必然导致"重绘"
浏览器会尽量把所有的变动集中在一起,排成一个队列,然后一次性执行,尽量避免多次重新渲染。操作样式时要注意合理的写法。
提高性能方法:
- DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。
- 不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式
- 尽量使用离线DOM,而不是真实的网面DOM,来改变元素样式
- 先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)
- position属性为absolute或fixed的元素,重排的开销会比较小
- 使用 window.requestAnimationFrame()、window.requestIdleCallback()
与 event loop 的关系
很多人不知道的是,重绘和回流其实和 Event loop 有关。
- 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
- 然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
- 判断是否触发了 media query
- 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行 requestAnimationFrame 回调
- 执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
- 更新界面
- 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 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 提升移动的元素。
- 避免过度使用提升规则;各层都需要内存和管理开销。
-
- 避免长时间运行输入处理程序;它们可能阻止滚动。
- 不要在输入处理程序中进行样式更改。
- 使处理程序去除抖动;存储事件值并在下一个 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头(通用信息头,响应头,实体头) – 响应报文主体
详细字段就不仔细介绍了,有兴趣的同学可自行查阅。
二、缓存机制
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中 以上两点是浏览器缓存机制的关键,确保了每个请求的缓存存入与读取。
浏览器缓存分为强缓存和协商缓存,强缓存会直接从浏览器里面拿数据,协商缓存会先访问服务器看缓存是否过期,再决定是否从浏览器里面拿数据。强缓存优于协商缓存。
强缓存
强制缓存 就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。强制缓存的情况主要有三种:
- 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)。
- 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存。
- 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果。
强制缓存的缓存规则是什么?
控制强制缓存的字段分别是 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秒后失效。
- must-revalidate: 如果超过了 max-age 的时间,必须向服务器请求验证资源是否还有效 cache-control 的值可以多个组合使用。
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
- 协商缓存生效,服务器返回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 标签来指定缓存行为
- 使用查询字符串来避免缓存。因为缓存有一些已知的问题,使用查询字符串会导致有些代理服务器不缓存资源。
缓存位置
内存缓存(memory cache) 和 硬盘缓存(disk cache)
- 内存缓存(memory cache):优点 快速读取和时效性:
- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
- 时效性:一旦该进程关闭,则该进程的内存则会清空。
- 硬盘缓存(disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
在浏览器中,浏览器会在 js 和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取;而 css 文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存。
参考文章:
彻底理解浏览器的缓存机制
浏览器缓存策略之扫盲篇
3. HTTP 状态码和常见请求头
- 20X: 成功处理请求,成功返回。
200请求成功 201请求成功,并创建一个新资源,一般是post/put请求 202请求接受,未响应无结果 203非授权 204无内容 205重置内容 206部分内容 - 30X: 重定向请求。
300多种选择 301永久重定向 302临时重定向 303查看其他位置 304未修改,用缓存 305代理 307临时重定向 - 40X: 请求错误。
400错误 401未授权 403禁止 404未找到 405方法禁用 406不接受 407需代理授权 408请求超时 409冲突 410已删除 411需要有效长度 412为满足条件 413请求实体过大 414请求URL过长 415不支持的媒体类型 416请求范围不合要求 417未满足期望值 429太多请求 431请求字段太大 - 50X: 服务器错误。
500服务器内部错误 501尚未实施 502错误网关 503服务不可用 504尚未实施 505HTTP版本不支持 511要求网络认证
常见请求头及其作用
- Accept 浏览器接受的格式
- Accept-Encoding: 浏览器接受的编码方式
- Accept-language: 浏览器接受的语言,用于服务器判断多语言
- Cache-Control: 控制缓存的时效性
- Connection: 连接方式,keep-alive 且服务端支持,则会复用 tcp 连接
- Host: HTTP 访问使用的域名
- If-Modified-Since: 上次访问时的更改时间,如果服务器认为此时间后没有更新,则会给出 304 响应
- If-None-Match: 上次访问的 E-Tag ,通常是页面的信息摘要,比更改时间更加准确一些
- User-Agent: 客户端标识
- Cookie: 客户端存储的 cookie 字符串
4. 跨域
跨域可以看我之前整理的文章,指路 Here
5. HTTP1.X 和 HTTP2
一、历史发展
HTTP 超文本传输协议,物如其名,主要就是为了将超文本标记语言,即 HTML 从 Web 服务器传送到浏览器。但随着时代的飞速发展,Web 2.0 的到来,页面更复杂,逻辑更庞大,当 ajax 的出现,又多了向服务器获取数据的方法,这些都是基于 HTTP 协议的。这使得我们不得不对 HTTP 不断地进行优化。
时间线:
1991 HTTP/0.9 -> 1996 HTTP/1.0 -> 1999 HTTP/1.1 -> 2015 HTTP/2
从 HTTP/1.0 到 HTTP/2,都是基于 TCP 作为底层协议进行通信的。
HTTP/1.1 也是当前使用最为广泛的 HTTP 协议。
二、HTTP 的基本优化
影响一个 HTTP 网络请求的因素主要有两个:带宽 和 延迟。
带宽: 现在网络基础建设已经使得带宽得到极大的提升,带宽影响已经不大了。
延迟:
-
浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,谷歌同时只能有 6 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。
-
DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用 DNS 缓存结果来达到减少这个时间的目的。
-
建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。
三、HTTP 1.0 和 HTTP 1.1 区别
-
长连接,HTTP 1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1 中默认开启 Connection: keep-alive,HTTP1.0 默认关闭。一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。
-
缓存处理,在 HTTP1.0 中主要使用 header 里的 If-Modified-Since, Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。
-
带宽优化及网络连接的使用,HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
-
错误通知的管理,在 HTTP1.1 中新增了 24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
-
Host 头处理,在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。
四、HTTP2.0
HTTP2 是 HTTP 协议自 1999 年 HTTP 1.1 发布后的首个更新,主要基于 SPDY 协议。
SPDY 协议
谷歌设计 SPDY 的目的在于降低网页的加载时间。优化了 HTTP1.X 的请求延迟,增加了安全性。
主要特点:
- 采取了多路复用,降低了延迟同时提高了带宽的利用率。
- 请求优先级
- header 压缩
- 基于HTTPS的加密协议传输,大大增加了安全性
- 服务端推送
HTTP2.0 和 SPDY 的区别:
- HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
- HTTP2.0 消息头的压缩算法采用 HPACK ,而非 SPDY 采用的 DEFLATE
2015年 Google 移除对SPDY的支持,拥抱 HTTP2
HTTP2 和 HTTP1.X 相比的新特性
1. 新的二进制格式(Binary Format)
HTTP1.x 的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定采用二进制格式,实现方便且健壮。
2. 多路复用(MultiPlexing)
在 HTTP1.X ,每个请求都会建立一个 tcp 连接,也就是每个请求都要进行三次握手,这就导致了时间和资源浪费。而且浏览器会限制同一域名下并发请求数量,当请求资源过多时,队头阻塞会导致后面的资源要等待其他资源请求完成后才能发起请求。(就像是出去买吃的,前面的点完单了去旁边等着就好,后面接着点餐。要是前面的不让开非要等到拿到餐了才走,那可不太坑了。之后介绍队首阻塞)针对此优化,把不同资源放在不同域名下,以此避开浏览器最大并发数的限制。
HTTP2 则使所有请求都会共用一个 tcp 连接,可以传输所有数据。解决了浏览器同一域名请求最大数问题,也减少了每次都要开 tcp 连接造成的资源和时间浪费。
共用一个 tcp 连接,那么问题来了
同一个 tcp 连接发送多个 http 请求,如何保证传输不会出错呢?
HTTP2 有两个概念,stream 流 和 frame 帧。 frame 是最小单位,构成 stream。每个 frame 都有一个 Stream Identifier 标识其属于哪个 stream。
多路复用,就是在一个 tcp 连接中可以存在多个 stream ,也就是多个请求。每个 stream 又包含多个 frame ,根据 Stream Identifier 可以识别其对应的 stream , 然后在服务端重新组合就可得到完整请求了。提高了传输性能也保证了传输的正确性。
简单来说: 多路复用,就是连接共享,一个 tcp 连接上通过流发送所有请求。每个请求都有唯一的 id ,保证正确性。
与 HTTP1.1 长连接区别
-
HTTP1.1 是同一个连接只能用一次, 如果开启了 keep-alive ,虽然可以用多次,但是同一时刻只能有一个 HTTP 请求。1.1 Pipeling 解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的队首阻塞。
-
HTTP2 就是在同一个 TCP 连接,同一时刻可以传输多个 HTTP 请求。就算某个请求任务耗时严重,不会影响到其它连接的正常执行。
为什么HTTP/1.1不能实现“多路复用”?
HTTP/2 是基于二进制“帧”的协议,HTTP/1.1是基于“文本分割”解析的协议。
多路复用有多好?
HTTP 性能优化的关键并不在于高带宽,而是低延迟。TCP 连接会随着时间进行自我「调谐」,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐则被称为 TCP 慢启动。由于这种原因,让原本就具有突发性和短时性的 HTTP 连接变的十分低效。HTTP/2 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接,让高带宽也能真正的服务于 HTTP 的性能提升。
3. Header压缩
在 HTTP/1 中,HTTP 请求和响应都是由「状态行、请求 / 响应头部、消息主体」三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件(例如图片、音频),但状态行和头部却没有经过任何压缩,直接以纯文本传输。越来越多的请求导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。
HTTP2 使用了 HPACK 压缩格式对 header 进行了编码,减少了大小。然后在浏览器和服务端共同维护一个字典,记录出现过的 header ,传输键名就可以了。大大减少了数据量。
具体内容可以看这篇文章 HTTP/2 头部压缩技术介绍
4. 服务端推送(server push)
服务端可以在响应客户端某个请求后,根据这个请求主动推送其他资源,减少 HTTP 请求。
服务端推送能把客户端所需要的资源伴随着 index.html 一起发送到客户端,省去了客户端重复请求的步骤。正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。
举个栗子:比如一个 html 页面关联一个 css 和 js 资源。 HTTP1.X 要请求三次,HTTP2 中只需要一次,服务器发现 html 包含 css 和 js 资源,便会将三个资源一起返回。
参考文章:
HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
五、常见问题
1. 请求是异步的,为什么会阻塞? 队首阻塞
这里的阻塞是 队首阻塞,队首的事情还未处理完时,后面的都要等待。
-
HTTP1.0 队首阻塞
对于同一个 TCP 连接,所有的请求放入队列中,只有第一个请求的响应收到了,然后才能发送下一个请求。发生在客户端。
-
HTTP1.1 队首阻塞
对于同一个 TCP 连接,一次可以发送多个请求,不想 1.0 那样等待响应。这解决 1.0 的队首阻塞。但是 HTTP1.1 规定,服务端响应的发送要根据请求被接收的顺序排队,先收到的请求其响应也要先发送。(就像我们去买东西一样,我先到了先给我哈哈哈哈,不敢说不行) 但是这样会造成问题是,如果最先收到的请求处理时间过长,响应生成变慢,就算其他请求的响应已经完成了,也只能等着,这就造成了响应的阻塞。 1.1 的队首阻塞发生在服务端。
2. 浏览器 HTTP 请求并发
页面10张 img,http1 是怎样的加载表现,怎么解决。那多域名又为什么可以解决呢?http2 是怎样表现?
http1 下,谷歌浏览器对一个域名下最大 tcp 连接数为 6,所以 10 张图片表现为 6 + 4。所以可以用多域名部署解决。5个 a 域名,5个 b 域名就可以实现一瞬间全部出来了(或者6个 a,4个 b,融会贯通)。如果是1个a域名,9个多域名,会表现为(6 + 1) + 3
http2 则是一起全部加载
并发请求
页面资源请求时,浏览器会同时和服务器建立多个 TCP 连接,在同一个 TCP 连接上顺序处理多个 HTTP 请求。所以浏览器的并发性就体现在可以建立多个 TCP 连接,来支持多个 HTTP 同时请求。
Chrome 浏览器最多允许对同一个域名 Host 建立 6 个 TCP 连接,不同的浏览器有所区别。
HTTP1.X ,每个 TCP 连接只能有一个 HTTP 请求,所以最多六个请求。HTTP2 多路复用可以更多。
建议阅读下该文章 浏览器HTTP请求并发数和TCP连接的关系
六. TCP
三次握手和四次挥手
参考文章 为什么TCP连接要三次握手,四次挥手
6. 简单介绍 HTTPS
什么是 HTTPS?
以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。
HTTPS 和 HTTP 的区别
-
HTTPS 协议需要到 CA 申请证书,一般免费证书较少,因而需要一定费用。
-
HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
-
HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
-
HTTP 的连接很简单,是无状态的;HTTPS 协议是由 SSL + HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。
为什么 HTTPS 比 HTTP 更安全?
-
在交换密钥使用非对称加密方式,防止密钥被非法获取。建立通信后,交换报文阶段则使用对称加密,防止报文被窃听。
-
通过数字签名解决了报文可能被篡改的问题。
数字签名作用:
- 能确定消息确实是由发送方签名并发出来的,假冒不了发送方签名。
- 数字签名能确定消息的完整性,证明数据未被篡改。
- 通过数字证书解决了通信方身份可能被伪装的问题。
优缺点
优点
- SEO 排名比 HTTP 网站高
- 安全性高
缺点
- 页面加载时间延长近 50%
- 并不是绝对安全,加密范围有限
- 需要费用,成本高
如何配置
未完待续...
7. 网络安全
谢谢观看,请多多指教。