说一下性能优化

1,187 阅读8分钟

性能优化

追求的是什么, 是你的网页可以 以最快的速度 打开, 比如说用户点一下啪的就开了点哪里哪里开, 什么操作都是立刻有反馈.

关键字是:速度

试想未来有一天, 到了 18G 时代, 每个人的网速都是 1000M 的, 那你还优化什么你的网站就算是 100M 大小也不怕.

可是那是遥远的未来, 当下网速还没有这么快。

所以我们的问题是: 在一个比较低一点的网速上面, 尽可能的快的加载出来我们的页面.

固定一个变量: 网速.

所以问题变成: 在固定网速为 p 的条件下, 如何尽可能的让我们的页面快一点加载出来.
假设我们的资源大小是 n, 网速为 p, 理论上将资源, 从服务器拿到客户端需要的时间
是:

time = n/p

p 固定, 那么 n 越小, time 越小. 也就是说: 我们的资源的体积越小, 时间越短.

所以问题变成: 如何减少我们资源的大小?先说我们的资源的种类有哪些?

images
js
css
html
fonts
others

依次看看怎么减小它们的体积:

图片压缩
js 压缩
css 压缩
html 压缩
fonts 删减
others 不知道

这些都是建立在你当前已经存在的资源的上面, 还可以做预处理:

  • 切图的时候就不要切高清无码图

  • 不要引入没有必要的 js, 或者说你为了兼容 Object.assign() 直接引了一个 babel-polyfill这样

  • 不要闭着眼就把 bootstrap 引入进来了...

上面的讨论, 实际上都是有一个前提的, 就是说: 所有的资源都从 server 到 client 之后, client 才能看见页面

但是这并不是真的:
有时只要你拿回来一部分资源的时候, 页面就可以显示出来了.
所以我们可以对资源做一些区分, 将 '页面显示出来所需要的最小资源集合' 优先返回回去,
剩下的资源再说, 这样也是一种思路。

所以我们的问题变成: 找到那个最小的资源集合, 或者说, 合理的安排资源的顺序。how?

依旧是那些资源类型:

images
js
css
html
fonts
others

images 完全可以做懒加载, 不需要那么早就扔出去. 
第一次要展示的页面是 pageA, 那么 pageB 相关的资源, 你就不要先返回啊.

这方面相关的一些概念是:

  • 首屏渲染

  • 按需加载

  • code spliting

  • critial css

  • ...

其实这句话说的好像是 server 去安排资源的顺序一样, 但是你也知道不是的, server说我就是无脑
扔, 你自己安排。
所以我们才会去让 link 提前加载, 让 script 在后面加载.

上面算是第一个阶段的结束, 现在我们更进一步前面说了:

time = 资源大小 / 网速

实际上这个模型可以比喻成这样:
小明从 A 点到 B 点拿一堆总重量为 100kg 的东西, 小明需要多久才能把东西全部从 B ->A?
和小明每次拿多少, 以及他一趟要多久相关, 是不是?
所以上面提及到的 '网速' 这个概念, 是一个混合变量

'网速' = ('带宽','请求相应时间')

带宽, 也就是每次拿多少, 一般都是假设是一个固定的值
那么就剩下请求响应时间了, 这个涉及到的点,
就是: 接口响应要快.

前面讨论的时候, 都是假设, 只有小明一个人在搬, 但是如果有 10个人呢?
这里涉及到的就是:
浏览器的并行加载

这玩意是浏览器提供的, 我们谈我们的性能优化手段, 貌似和他也没有关系, 这个是浏览器做的事情.
不不不, 我们可以利用.
从这个角度来看, 是不是并行加载的数量越多越好? 就是的.
那么我们可以尝试去提高并行加载的数量啊, 比如说从 1 提高到 100
这里涉及到的是:
浏览器的并行加载是以 domain 为区分而限制的, 一个 domain 最多可以支持 4个(不同浏览器不同)
并行加载.

那么我们把资源分成多个域名不就可以了吗? 好像是这个道理.
你有100个资源, 划分成 25个域名, 同一个域名下面支持 4个并行, 那你不是一瞬间100个都发出去了吗
那速度不是唰的一下就上来了吗? (这块没弄明白)

那再想想, 还有什么办法能让时间更短一点?
让 A 跟 B 近一点啊.
这个就是 CDN.
也就是说, 我们可以上 CDN, 这个的确是我们做的.


上面的都是说第一次加载, 第一次访问的时候, 还有第二次第三次访问的情况, 这个时候就涉及到缓存了。

我们说下缓存以我记忆里面的概念, 在性能优化的时候提到的缓存一般有三种情况:

  1. 浏览器缓存

  2. 通过使用 storage 进行缓存

  3. Ajax 缓存

为什么要缓存?
因为当你第一次访问一个网站, 请求了 100个资源.
然后你第二次又访问了这个网站, 又请求了 100个资源.

把这两个 '100个资源'做并集, 就会发现并集里面有很多资源.
也就是说你在两次访问这个网站的时候, 对于同一个资源, 你请求了两次.
这肯定是不必要的, 浪费的. 所以我们完全可以搞起来.

怎么搞?

  1. 拿到多次访问的时候, 每次请求的资源, 作为一个集合

  2. 做这些集合的并集

  3. 得到的集合, 就是在这多次访问中都没有变化的资源, 称为 A

我们的目标就是: 让 A 中所有的资源都仅仅请求一次就好了.
1 得到 A
我们要怎么得到 A ? 资源是我们弄出来的啊, 代码是你写的,
你对资源都很清楚的, 所以你自己就知道哪些资源是一直都不变的, 哪些资源是一段时间就变的
哪些是每次都变的, 你很清楚啊.

好吧, 那我给你一个所有的不变的资源 A

2 再说下缓存是谁缓存? 浏览器啊, 那浏览器怎么知道这一堆资源里面哪个要缓存, 哪个不要?
你知道, 所以你要去告诉他.
你怎么告诉他? http 协议啊, 那只能是 http headers 字段里面标记了啊.
这里不具体说 协议头长什么样子, 说下方案:

server 告诉 client, 这个文件要缓存, 这个文件什么时候过期
在下一次访问的时候, 浏览器请求的时候呢发现了这个文件, 看看它什么时候过期, 发现没有过期
那就用, 哎一发现过期了, 那么就去重新请求. 就是保质期的意思.

第二种方案是 storage 做缓存, 我印象中记得看见过两个:
微信
美团的 LsLoader

第三种就是 Ajax 请求缓存, 通常见于 query 类型的接口的缓存, 比如说商品列表等.

最后一个讨论点, 我们之前只是说资源拿回来, 页面显示, 但是这两者之间, 还有个事情:浏览器需要去渲染资源

我们说浏览器的渲染过程, 到底在说什么, 是在说整个流程, 输入资源, 输出页面.步骤大体上可以这么描述

   输入html -> 解析   
                  -> 合并成 render tree -> layout -> paint ->结束.
   输入css  -> 解析 

layout: 就是安排 render tree 上面的每一个节点的位置, 大小paint: 就是绘制每一个节点.

流程是这样, 那还有啥问题.

(1) 下载和解析是不是同步的, 不是, 是边下载边解析, 不需要等到所有的资源都下载结束才解析也就是说: 来了 html 就解析html, 来了 css 就解析 css

(2) 在渲染的过程中, 如果遇见 js 怎么办? 是继续渲染还是停下, 先执行 js?其实就两个答案, 要么继续, 要么停下来.

继续的话, 就是说让 js 在我渲染之后再执行, 别着急, 好的, 渲染结束之后, js 里面唰的一下执行一下:

document.write('');

这意味着, 我所有的渲染的努力都白费了.
与此相比, 在渲染的时候遇见 js 等一下, 等它执行结束, 再继续执行, 这样防止全盘努力白费,
风险小点, 所以还是等吧.

所以在渲染的过程中, 如果遇见 js, 那么就停下来, 执行它.

那能不能让 js 告诉我它到底有没有就是说, 更改 DOM, 有时候它的确不会啊, 这个时候我就白等了.所以有 defer 和 async

这个就相当于说,
defer, defer啊, 告诉你, 你不用等我, 你继续渲染, 我不会改你的, ok
这个时候, js 就会在浏览器渲染之后再执行.

async 呢?这个就坑爹了, 这个说, 你别等我, 我也不等你, 我反正下下来之后就执行