Hexo - 性能优化之旅

前几天面试问了性能优化,好久没关注这块了,许多操作就想不起来,很尴尬……

所以本文总结一下 hexo-theme-zhaoo 主题中做的一些性能优化,主要从 用户体验网络请求视图渲染 三方面入手,作者借此复习一下,逃。

用户体验

在做性能优化的时候,我们总是会关注各种指标:请求速度、渲染速度、白屏时间……这些优化固然重要,但是我们往往会忽略 用户体验,从用户角度来看可能体验才是最重要的。

试想:你的网站刚上线,并没有做任何优化,此时首屏加载时间需要 8s 。很遗憾,大多数用户没有这个耐心等待,会选择点击右上角的 X ,并且拉入了“黑名单”。这样可不行,然后你就做了一些常规优化,轻轻松松就将首屏加载时间拉到了 2s ,用户粘性就上来了。这时候你还不满足,执着于各种性能指标,通过一套“牛X”的算法把加载时间优化到了 1.5s 。过程可能很艰辛,但是结果却不是很理想,因为对于大多数用户来说可能根本感受不到这 0.5s 之间的差别。当你绝望的时候,UI小姐姐甩过来一张 Loading 图,你随手一加,结果用户量猛增。

从用户心理角度来说,从按下回车到首屏展示这段时间(白屏),他是最不安的,觉得这个网站随时可能挂掉。你甩出一个 Loading 动画让他“欣赏”,他起码会觉得这个网站还在动,大不了多看会儿嘛。

所以我们做优化的时候要兼顾 用户体验 ,而不是一昧的追求速度。

Loading

思路很简单,页面头部首先加载 Loading 遮罩图(HTML + CSS),在页面底部加载 JS 后,通过 JSLoading 遮罩移除即可。

考虑到用户体验,提供一个较为稳定的动画时间,而不是一闪而过,所以给了 0.5s 延时。同时埋了个点,可以在 _config.yml 中配置自己的 Loading 效果。

loading

渐进动画

网络请求

Promise 并发控制

这是一道“字节”必考题,面经背了学以致用吧。

主题使用 leancloud 来存储浏览量(PV),在进入页面的时候会并发请求获取浏览量。会产生以下问题:1. 并发请求会被服务器阻塞,返回 429 错误。2. 由于请求被阻塞了,页面渲染也被JS引擎阻塞了,产生 1s 左右的延迟。

429

429

为了解决这一问题,我们可以引入 Promise 并发控制请求流量,控制一次性不会涌入大量请求。

并发控制

调用方式

CDN

静态资源从服务器下载到用户主机,需要经过许多路由,一般距离越远,速度越慢。CDN(内容分发网络) 就是将资源部署到空间分布于各地的服务器上,用户访问时从就近的服务器拉取资源,起到加速作用。还可充当 负载均衡缓存备份 功能。(以上是官方说法,我们哪来那么大的流量,纯粹是因为学生服务器太渣了,上行速度实在是太慢,架不住资源)

CDN 直接用云服务商的就好了,阿里云、腾讯云、七牛云、又拍云…… 好,我选择免费的七牛云,HTTPS 每个月也就五毛钱。

部署方式很简单,创个账号,建个空间,绑个域名,传个文件,网上一堆教程。

推荐一个图床工具:PicGovs-picgo

VSCode 版很方便。众所周知:VSCode 除了码代码啥都可以干。

另外,JSCSS 等静态资源,我们就不用存自己的 CDN 空间了,BootCDN 更方便。我在主题埋了个点,直接填写链接即可,不填默认从本地获取。

BootCDN

Gzip

服务器层面,应该开启 Gzip 压缩,能将文件资源体积压缩到原来的 1/4 。注意不要对图片资源进行压缩,适得其反,所以在配置项 gzip_types 中进行选择。

gzip

gzip

(宝塔面板偷个懒)

async / defer

【亲测效果很好】

我们知道,GUI渲染线程JS引擎线程 是互斥的,同时执行会导致渲染混乱,所以默认将他们分离执行。运行到 <script> 标签(包括加载脚本)时会暂停渲染,浪费了一些性能。(这也是我们提倡将 <style> 放在头部,将 <script> 放在底部的原因之一)

为了压榨性能呢,w3c 为我们提供了两个属性:asyncdefer,可以帮助我们异步加载脚本。(周立齐:执行异步是不可能,这辈子都不可能的)

  • default:同步加载脚本
  • aysnc:异步加载脚本,加载完马上执行
  • defer:异步加载脚本,页面渲染后执行

盗了张图,一图胜千言:

async/defer

优化思路:

  1. 把一些渲染过程中用不到的第三方脚本打上 defer ,拖到渲染后执行。
  2. 把渲染中需要使用的脚本打上 async ,异步加载。(需要注意顺序,不要打乱执行流程)

async/defer

优雅一点嘛~

async/defer

评论请求统计DaoVoice 这些第三方脚本加载巨慢,特别是 leancloud ,延迟大约 1s 多,严重拖慢了渲染速度。套上 async/defer 之后感觉棒棒哒~

loadScript

一些例如 实例化挂载 等执行逻辑,需要依赖前置脚本加载完后才能执行。这时候如果给它俩都套上了 async/defer ,异步执行顺序就会不可控,会报一些 underfined 之类的错。

所以我们需要改造一下,让它们链式触发。最简单的思路就是借助回调函数了,可以直接使用 jQuery$.ajax() 实现。但是我后期考虑移除 jQuery,还是自己封装一下吧。

最简单的加载方式是 loadScript ,通过操纵 DOMappend 一个 <script> 标签来加载资源。

loadScript

食用方式:

loadScript

AJAX

阿贾克斯年代了, loadScript 也太逊了吧……

之后可能会考虑移除 jQuery, 那就先封装一个 Ajax吧:

PJAX

这是个反面教材……

PJAX 可以理解为 AJAX 的升级版。AJAX 只是简单的异步请求资源,一般用于局部无刷新加载,如果用于整页加载会造成 URL 无法更新,从而影响 SEO 和 回退操作。而 PJAX 基于 AJAXpushState (其实就是前端路由那两套方案),可以改变 URL,可以提供了整页无刷新加载。

缓存

PWA

视图渲染

静态脚本移到编译阶段

减少重绘回流

图片懒加载

懒加载就是图片延迟加载,先套个占位图,等图片 进入视口后 或者 快进入时候时(通过加一定高度预判,预加载) 才加载并渲染图片。

实现思路:

  1. 在编译阶段进行劫持,将 HTML 中的 img 标签中的 src 图片地址保存到 data-src 进行缓存,同时替换为占位图(Loading)。
    编译替换
  2. 运行阶段,图片进入视口后,再将 src 替换为 data-src 加载图片。
    懒加载

    (暂时用了第三方懒加载库,立个Flag,日后定要造个轮子)

图标

查看评论