从优化到面试装逼指南——网络系列

16,087 阅读22分钟

与普通的面试文不同,从问题出发彻底了解原理到实践,让你能在面试现场直接装逼。
ps: 我好像也装了个逼?

此文深入阅读需要15分钟左右请各位不要再带薪如厕的时候观看,以免痔疮——用心微医

狗蛋来到了面试场地,面试官是个凶神恶煞的纹身男人,心里充满了胆怯,感觉亚历山大~~

面试官:来,说说你觉得优化什么最能给你的项目提供质的飞跃?

狗蛋:H T T P 资源

面试官冷笑😏小伙子开始挖坑了

面试官:怎么优化?

狗蛋:俩个方向,减少请求数或资源大小

面试官男人:具体有哪些呢?


减少资源体积

gzip

gzip 使用了 LZ77 算法与 Huffman 编码来压缩文件,重复度越高的文件可压缩的空间就越大

开启gzip可以压缩代码,如何查看是否开启gzip?

—— 打开控制面板进入NetWork,右键选取response headers 选择查看Content-Encoding

Content-Encoding:内容编码格式gzip 和 deflate

1.首先浏览器(也就是客户端)发送请求时,通过Accept-Encoding带上自己支持的内容编码格式列表
2.服务端在接收到请求后,从中挑选出一种用来对响应信息进行编码,并通过Content-Encoding来说明服务端选定的编码信息
3.浏览器在拿到响应正文后,依据Content-Encoding进行解压。

案例


我们可以看到图片中开启gzip压缩之后文件小了很多(越小的文件越不明显)

优化

nginx开启

列出了一些重点

gzip on;
gzip_min_length 1k; //不压缩临界值,大于1K的才压缩,一般不用改
gzip_comp_level 2//压缩级别,1-10,数字越大压缩的越细,时间也越长
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; //进行压缩的文件类型
gzip_disable "MSIE [1-6]\.";//ie兼容性不好所以放弃

webpack开启

compression-webpack-plugin 这个插件可以提供功能,伸手党福利

const CompressionWebpackPlugin = require('compression-webpack-plugin');
plugins.push(
    new CompressionWebpackPlugin({
        asset'[path].gz[query]',// 目标文件名
        algorithm: 'gzip',// 使用gzip压缩
        test: new RegExp(
            '\\.(js|css)$' // 压缩 js 与 css
        ),
        threshold10240,// 资源文件大于10240B=10kB时会被压缩
        minRatio: 0.8 // 最小压缩比达到0.8时才会被压缩
    })
);

面试官:webpack的gzip和nginx的有什么关系?

1.nginx没有开启gzip压缩,webpack打包出的.gz文件是用不到的
2.nginx开启了gzipnginx查找静态资源是否存在已经压缩好的gzip压缩文件,如果没有则自行压缩(消耗cpu但感知比较少)
3.nginx开启gzip压缩,webpack打包出的.gz文件被找到,提前(打包)压缩直接使用,减少了nginx的压缩损耗

深入

面试官:gzip是怎么压缩的?

到这一步的都不是一般人了,我就简述一下:使用"滑动窗口"的方法,来寻找文件中的每一个匹配长度达到最小匹配的串
,重复的内容以一个哈希值存储在字典表中并替换到匹配的串上,以此来达到压缩,因此重复度越高的文件可压缩的空间就越大。

源文件控制

这里有很多方案,例如我们常说的webpack利用splitchunks进行拆包配合一些加载方式,使原本特别大的appjs分成若干个细小的包,webpack优化将会在下一期的专题里讲到。

图片

webp,是一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式,无损压缩后的WebP比PNG文件少了26%的体积,有损压缩后的WebP图片相比于等效质量指标的JPEG图片减少了25%~34%的体积。

唯一的问题可能就是兼容性了!!万恶的兼容性我与你不共戴天

还有一种方案,在上古时代我们也会采用直接有损压缩图片来降低体积
zhainanba.net/go/13513

http2——头部压缩

HTTP 请求和响应都是由「状态行、请求 / 响应头部、消息主体」三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件(例如图片、音频),但状态行和头部却没有经过任何压缩,直接以纯文本传输。
一个网站内总有多个http请求,而其headers所占的字节也不少,尤其cookie,有些时headers甚至超过了主体大小,


随意打开一个网址,看看这张图是不是基本上大部分http的headers内容都是重复的,

深入

面试官:头部压缩是怎么实现的?

帧的感念会在后面讲到。


类似于对象键值对的形式:

1、完全匹配的头部键值对例如 :method: GET,可以直接使用一个字符表示
2)HTTP/2 中的静态字典大致如下:

    method    GET
    path    /index.html
    scheme    https
    cookie

同时,浏览器可以告知服务端,将 cookie: xxxxxxx 添加到动态字典中,这样后续整个键值对就可以使用一个字符表示了。
类似的,服务端也可以更新对方的动态字典。需要注意的是,动态字典上下文有关,需要为每个 HTTP/2 连接维护不同的字典。

其中静态字典在首次请求中就可以使用,对于静态、动态字典中不存在的内容,还可以使用哈夫曼编码来减小体积。
HTTP/2 使用了一份静态哈夫曼码表,需要内置在客户端和服务端之中。

减少资源请求

DNS

dns是什么?

是将域名解析为ip,例如访问baidu.com通过dns系统查出他的ip地址,才能访问,主要是ip对于我们用户太难记住,dns做了域名到ip的解析。

在DNS查询过程中,浏览器进入等待,白屏时间过长,如果DNS查询过多,会受到性能影响,因此需要用到DNS缓存。

深入

面试官:dns是怎么运作的?

1.浏览器是否有缓存
2.操作系统是否缓存,常见的如hosts文件
3.路由器是否有缓存
4.域名服务器是否有缓存:根域服务器(.) -> 顶级域名服务器(com->主域名服务器(baidu.com

优化

<link rel="dns-prefetch" href="//baidu.com">

需要注意的是,虽然使用 DNS Prefetch 能够加快页面的解析速度,但是也不能滥用,因为有开发者指出 禁用DNS 预读取能节省每月100亿的DNS查询 。

要禁止隐式的 DNS Prefetch:

<meta http-equiv="x-dns-prefetch-control" content="off">

http协议

主要还是http2的优化,

核心 二进制分层
为什么说是核心主要是因为http2的一些优化都是基于二进制分层实现的

帧(Frame)是 HTTP2 通讯中的最小传输单位,所有帧以固定的 9 个八位字节头部开头,随后是一个可变长度的有效载荷


如上图将一个http消息拆分成DATA帧 和 HEADERS帧。

多路复用

当一个tcp建立连接之后,http2将多个请求重复利用这个tcp,并且分成多个stream交错传输,慢的请求或者先发送的请求不会阻塞其他请求的返回,最终根据stream的标识再重组返回,类似并发的感觉非并行。

Header 帧必须在 data 帧前面,data 帧依赖 header 帧的信息解析

深入

面试官:http2多路复用和http1.x的keep-alive有什么区别?

乍一看好像都是复用了TCP连接,但俩这是截然不同的。

http1.x
    它是遵循先进先出,服务端只能按顺序响应请求,所以如果前面的请求没有响应完变灰发生 队头阻塞,造成延迟,同时保持不必要的连接会影响服务器性能,同时浏览器限制了http同时并发的上限。
http2
    多个请求可以同时发送(不分先后),按序响应,解决1.x的一些问题。

服务推送

服务端可以在发送页面HTML时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应,客户端可以通过发送一个 RST_STREAM 帧来中止推送。

案例

服务端可以主动把JS和CSS文件推送给客户端,而不需要客户端解析HTML时再发送这些请求。

实现:

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    http2_push /Home.css;     //推送css
    http2_push /Home.png;     //推送图片
  }

深入

面试官:为啥你公司还没启用http2???

大胸弟前端nginx配一下就完事,你有没有考虑过我后端老哥的感受?

  1. nginx 1.1 及其更高版本(nginx会做优雅降级,如果不支持则采用1.1)
  2. 首先我需要https的支持
  3. jetty 9.3 及其更高版本
  4. Apache 2.4.17 及其更高版本

如果你想升级但是无奈服务端不支持怎么办?

方案来了:
  client->nginx http2.0    
  nginx->service http1.1

缓存

这块都是被人问(写)烂的题目了,如果你还不清楚,需要重新审视(抽打)下自己了

若是http的code码为304,意味着命中了缓存。

缓存流程图

强缓存: Expires、Cache-Control

比较强硬,命中了缓存规则就直接缓存

面试官:Expires和Cache-control的区别

  Expires 如:Thu, 01 Dec 1994 16:00:00 GMT
  表示资源的具体过期时间,过期了就得向服务端发请求,
  然而服务器资源和电脑本地时间不同步会导致缓存更新策略不一致,例如用户自己修改时间。

  Cache-control,指定从请求的时间开始,允许获取的响应被重用的最长时间(单位:秒)。
  例如,max-age=60 表示可在接下来的 60 秒缓存和重用响应。

  若俩者同时存在Expires则被Cache-Control的max-age覆盖

深入

面试官:Cache-control有哪些属性了解过吗

  no-cache: 数据内容不能被缓存,每次请求都重新访问服务器, 若有max-age,则缓存期间不访问服务器
  no-store: 不仅不能缓存,连暂存也不可以(即: 临时文件夹中不能暂存该资源).
  private(默认): 只能在浏览器中缓存,只有在第一次请求的时候才访问服务器,若有max-age, 则缓存期间不访问服务器
  public: 可以被任何缓存区缓存, 如: 浏览器、服务器、代理服务器等
  max-age: 相对过期时间, 即以秒为单位的缓存时间
  s-maxage: 只用于共享缓存,比如CDN缓存(s -> share)。与max-age 的区别是:max-age用于相对过期时间的普通缓存
            而s-maxage用于代理缓存。如果存在s-maxage,则会覆盖max-age 和 Expires

资源缓存超出了强缓寸限定的时间便是协商缓存了

协商缓存: Last-Modified、ETag

顾名思义,可以以与服务器协商是否缓存

协商缓存有俩对:

 If-Modified-since: Last-Modifed

 If-None-Match: Etag

Last-Modified

浏览器请求 → 服务器返回Last-Modified字段(最后修改时间) → 浏览器再次请求 → 浏览器请求头携带字段If-Modified-Since:服务器返回的最后修改时间 → 服务器拿If-Modified-Since和服务器中资源最后修改时间对比 → 相等返回304读取缓存,小于最后修改时间重复第一次访问

ETag
浏览器请求 → 服务器返回ETagd字段(根据当前文件的内容生成的唯一标识码) → 浏览器再次请求 → 浏览器请求头携带字段If-None-Match:唯一标识码 → 服务器拿If-None-Match和服务器中资源当前ETag对比 → 相等返回304读取缓存,不等说明资源被更新,需要重新请求

深入

面试官: Last-Modified和ETag的区别

Last-Modified在秒级改变的情况下是无法更新的,也就是说如果文件在 1 秒内改变了多次,是无法监听到变化的,可能导致文件被缓存住。

但是Last-Modified的计算方式比Etag简单,原因上面也说过了时间点和计算唯一标识哈希码的算法难度是不同的,所以性能上是Last-Modified占优

服务器优先级ETag>Last-Modified。

复习

你看懂里面的缓存属性了吗?

localStorage、sessionStorage

浏览器级别的本地存储,都存在于用户本地浏览器中,可以通过浏览器中Application的storage查看

storage的大小完爆cookie(storage为5M,cookie4k),同时也是对应一个域名,

注意,如果使用localstorage永久存储的内容非常庞大注意利用代码定期释放

只存在客户端不参与服务端通信,因此在身份校验上避免了 Cookie 的安全问题。

    const Interviewer = { name:"李狗蛋"position:"FE"};
    // 设置
    sessionStorage.setItem("Interviewer"JSON.stringify(Interviewer));
    localStorage.setItem("Interviewer"JSON.stringify(Interviewer));
    // 读取
    const localviewer = sessionStorage.getItem("Interviewer");
    const sessionviewer = localStorage.getItem("Interviewer");

深入

面试官: localStorage和sessionStorage的区别,以及应用场景

localStorage:浏览器关闭依旧存在
1.存储token
2.资源存储
3.用户习惯存储

sessionStorage:浏览器关闭随之消失
1.当前数据存储节省http请求
2.翻页,前后数据存储

cdn


如图,cdn就是我们所说的就近原则。

深入

面试官: 说说cdn原理

基于智能DNS对用户的IP地址综合参考规则和数值指标的解析,将用户调度到择最快的Cache服务器

说人话!:选取最近站点进行请求,节省了对源站的消耗(回源)

浏览器同一时间段对同一个域名请求文件的最大下载数是有限的,可以利用多域名来存放不同的静态资源,增大页面加载时资源的并行下载数,缩短页面资源加载的时间,通常把图片放在一个专用的cdn域名就是一种解决方案。

cdn还能做缓存、防止攻击等等,想详细了解的不如参考下业界大厂提供的cdn方案

图片

面试官:区别于减少图片体积,减少图片资源请求的方案有哪些?

1.雪碧图
2.图片转base64

雪碧图

就是把多个图片拼在一起的图片,利用背景定位的方式获取自己想要的,常用于icon合集,这样通过一张图片可以减少多个icon的http请求。

当然这个方案的弊端就是background-position很麻烦啊还要算呐,人家不想要那么麻烦啊~

如果采用HTTP2的话,这个请求头阻塞的问题当然能解决了

base64

Base64是一种基于选用了"A-Z、a-z、0-9、+、/"64个可打印字符来表示二进制数据的表示方法。
Base64最初的又来是因为二进制文件包含很多无法显示和打印的字符,Base64便能将二进制字符串转换并显示,例如用记事本打开exe、jpg、pdf这些文件时,会有无法显示的二进制数据。

最新的浏览器自带了两个方法用于base64的编码和解码

分别是atob(Base64转成ASCII)和btoa (将ASCII转成Base64)

深入

面试官: base64如何转码及应用场景?

Base64转码是将4个字节转换成3个字节.先读入4个6位(用或运算),每次左移6位,再右移3次,每次8位,这样就还原了。

场景:

  1. 简单的数据编码
  2. 内容无错传输
  3. 电子邮件(SMTP协议)
  4. 图片base64编码

面试官:图片转base64是为了达到什么目的?

比较小的图片例如icon,使用base64编码,可以减少一次图片的网络请求;比较大的图片,使用base64编码会和html混在一起,加大了html页面的大小,反而加大了下载页面的大小,因此url-loader提供设置limit的功能

base64编码的图片不能像正常的图片可以进行缓存,但写在css里面可以让浏览器对css文件进行缓存

怎么实现?

1. window.btoa(str)
2. canvas.toDataURL();
3. var reader = new FileReader();
   imgUrlBase64 =reader.readAsDataURL(file);
4. webpack:url-loader 

异步加载

defer、async、module

<script async src="index1.js"></script>
<script defer src="index2.js"></script>
<script type="module" src="index3.js"></script>

先来了解下 DOMContentLoaded

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,并且DOM树已经生成好了。
但是其他外部资源,如样式文件、图片、字体等并没有加载好。

defer DOMContentLoaded事件之前执行,按照 defer 脚本的声明顺序来执行脚本(排队执行)。ps.根据不同浏览器不一定总是在DOMContentLoaded事件之前执行,所以最好只包含一个延迟脚本

async 下载完便执行,因此会中断渲染,因此async是不能保证顺序的。

module/nomodule 浏览器直接支持es6代码,也支持defer(默认)和async。

介于浏览器兼容性问题不建议全部采用module来优化文件大小(减少转码体积)

上图复习:

dom渲染
下载
执行
三种颜色对应下图横线

深入

面试官:说说 defer 和 async 的使用场景

不难揣测,defer肯定是更加适合多个js互相依赖且有执行顺序的场景,大多数情况都适用,包括各大脚手架也是默认defer(页面底部),业务代码最合适。

async则更加适合存放sdk或者其他毫不相关的第三方资源。

交互方案

  • 图片懒加载
  • 瀑布流
  • 滚动加载

异步加载方案千千万,万变不离其宗,这里就不展开细说,注意节流和防抖。下面推荐个不错的api

Intersection Observer 具体可以看MDN

预加载

preload

<link rel="preload" href="style.css" as="style">

这个指令可以在 link标签 中使用,如上文,表示预先请求当前页面需要的资源。

截图MDN对preload的可预加载内容的介绍:


当标签中含有as 属性则表示优先级最高记号,没有则被降级为普通预加载

prefetch

浏览器在空闲时(没有在请求时)获取设置prefetch的资源,并且将他们存储在浏览器的缓存中,当使用了含有 prefetch的静态资源,便从缓存中加载内容。

深入

面试官:preload 和 prefetch 的共同点和不同点

共同点

异步预加载
页面加载中发现资源含有 preload 或者 prefetch 时,会从网络堆栈传输到 HTTP 缓存并进入渲染器的内存缓存。

若资源含有缓存字段(例如,存在有效的 cache-control 和 max-age),它将存储在 HTTP 缓存中,可用于当前和未来触发它的请求;
若是没有设置缓存则它会被缓存到内存缓存中并保持不变直到它被使用。

不同点

preload:  直接请求
prefetch: 空闲时间请求

注意使用这俩个属性的必要性,若是没有用到,这是额外的带宽浪费,属于负优化,尤其移动端的流量浪费

安全方向

这是个不得不提起的面试问题

https

痛点

HTTP 是无状态的明文传输,https提供了加密传输、身份认证等功能,相对来说是基本不能破解的

与http区别


如图,我们看到在tcp到http中加加了一层 SSL\TLS

  • HTTP 的端口号是 80,HTTPS 是 443
  • 使用 HTTPS 是收费的,主要是因为中间的ca证书机构

结合下面问题看图思考,帮你彻底深入理解

名词解释

对称密钥: 相同的密钥可以用于信息的加密和解密
非对称密钥:一个私钥一个公钥。用私钥加密的数据,只有对应的公钥才能解密,用公钥加密的数据, 只有对应的私钥才能解密。

深入

面试官:为什么要有ca机构?

防监听
http是明文的,而https监听得到的数据是密文。

防中间人攻击
在服务端和客户端中间进行劫持通讯,伪装为客户端伪装和服务器伪装,再加以篡改的攻击方式。

** ca机构怎么解决 **
证书由第三方权威机构颁布,在客户端浏览器中存在ca根证书的公钥
tcp握手的时候,服务端会返回自己包装过的ca证书,
ca证书包装包含了申请者公钥、申请者的组织信息和个人信息、签发机构 CA 的信息、有效时间、证书序列号等信息的明文和CA私钥对信息摘要加密的签名,
最主要是携带了公司信息、域名、
服务器信息,因此即使通过了劫持也无法完美伪造。

面试官:为什么通过ca验证后客户端生成的是随机对称密钥而不是非对称密钥?

HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。

非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。

同时,在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以 HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密。

基本攻击

知道你们学不动了,我就精简一下,详细可以查看其他文章!

跨站脚本攻击XSS(cross site script)

xss就是把他人的恶意脚本注入到你的页面中

  • 反射型:服务端返回脚本,客户端执行(通过URL参数直接注入)
  • 存储型:后台存储,前端展示(发帖或评论输入内容处,存储到数据库后读取时注入)
  • DOM型:基于DOM(流量劫持、DNS劫持,修改页面脚本结构)

防御方案

  • http-only:在 cookie 中设置 HttpOnly 属性后,js脚本将无法读取到 cookie 信息。
  • 过滤与转译:例如掘金,是有评论功能的然而我们的alert(1)并没有执行不是么?前端是需要对敏感文字做处理的,.innerHTML、.outerHTML、document.write()、eval()、setTimeout()、setInterval()、v-html(vue)、dangerouslySetInnerHTML(react)等甚至 pre 标签也都会直接运行代码需要绝对注意字符串的内容。

推荐一个 xss 的npm包

深入

sql注入是啥

这不是后端知识吗?很多同学下意识的就怂了

别紧张其实就是之前说过的xss,只不过注入的是sql命令语句,
达到欺骗服务器执行恶意的SQL命令。

跨站请求伪造CSRF(cross site request forgery)/XSRF

结余成本和复杂度,这种攻击手段相较于xss偏少

盗用身份进行伪造,做一些恶意操作

  • 伪造身份:截取cookie、获取用户信息他用、盗用账号、非法交易、转账
  • 跨站点: img等标签跨域GET请求、POST自动表单提交、恶意链接跳转,利用登录态及cookie完成跨站伪造请求

防御方案

最主要是第三方的冒充,所以要做好身份认证的防范措施

  • 认证手段:验证 Referer、图片验证、活体认证、语音提示、人脸识别巴拉巴拉…
  • token: 页面插入token(隐藏的一个标签内,通常在表单input中,而不是在cookie中),JWT(JSON Web Token)

结束撒花✿✿ヽ(°▽°)ノ✿

面试官:不错不错,明天去保安大队报道

狗蛋:???

投递简历砸我邮箱 yangyz1@guahao.com


接近明年的跳槽期又近了一步,现在不抓紧什么时候抓紧,关注我,微医保你在求职路上药到病除,一路飞升

素质四连,点赞、关注、转发、评论

了解更多,关注技术公众号:ihap 技术黑洞 !!!