前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Hexo异步加载方案

Hexo异步加载方案

作者头像
Akilar
发布2021-06-11 10:43:40
1.7K0
发布2021-06-11 10:43:40
举报
文章被收录于专栏:Akilarの糖果屋Akilarの糖果屋

写在最前

在博客魔改过程中,不可避免的会引入大量的第三方脚本(js),而基于页面读取js的加载顺序,每当浏览器在加载html的过程中遇到<script>js代码片段</script>这样的标签时,浏览器会暂停继续构建html,而是优先执行当前的js脚本,等执行完毕后再继续加载后面的html。

至于外部脚本 <script src="js外链"></script>这样的写法,更是要先下载脚本,然后再执行,之后才能继续处理剩余的页面。

无形中,多出了一大把的加载时间。这也是我们常说魔改是对博客速度的负优化的原因之一。

因此可以通过给<script></script>添加deferansyc属性来实现异步加载,调整js的加载时间和顺序,确保浏览器构建HTML的过程一切顺利。

参考内容

  1. javascript.INFO——script-async-defer
  1. script中defer跟async是什么?
  2. CSS异步加载最简单的实现方式
  3. 异步加载CSS

原理分析

首先要清楚defer、async是什么,有什么区别。 deferasync<script>标签的两个属性,用来控制js脚本的加载。 以下先引用参考教程的原文。

教程原文

defer

defer特性告诉浏览器不要等待脚本。相反,浏览器将继续处理HTML,构建DOM。脚本会在后台下载,然后等DOM构建完成后,脚本才会执行。

这是与上面那个相同的示例,但是带有defer特性:

代码语言:javascript
复制
<p>...content before script...</p>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- 立即可见 -->
<p>...content after script...</p>

换句话说:

  1. 具有defer特性的脚本不会阻塞页面。
  2. 具有defer特性的脚本总是要等到DOM解析完毕,但在DOMContentLoaded事件之前执行。 下面这个示例演示了上面所说的第二句话:
代码语言:javascript
复制
<p>...content before scripts...</p>

<script>
  document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!"));
</script>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<p>...content after scripts...</p>
  1. 页面内容立即显示。
  2. DOMContentLoaded事件处理程序等待具有defer特性的脚本执行完成。它仅在脚本下载且执行结束后才会被触发。 具有defer特性的脚本保持其相对顺序,就像常规脚本一样。

假设,我们有两个具有defer特性的脚本:long.js在前,small.js在后。

代码语言:javascript
复制
<script defer src="https://javascript.info/article/script-async-defer/long.js"></script>
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>

浏览器扫描页面寻找脚本,然后并行下载它们,以提高性能。因此,在上面的示例中,两个脚本是并行下载的。small.js可能会先下载完成。

……但是,defer特性除了告诉浏览器不要阻塞页面之外,还可以确保脚本执行的相对顺序。因此,即使 small.js先加载完成,它也需要等到long.js执行结束才会被执行。

当我们需要先加载JavaScript库,然后再加载依赖于它的脚本时,这可能会很有用。

defer特性仅适用于外部脚本 如果<script>脚本没有src,则会忽略defer特性。

async

async特性与defer有些类似。它也能够让脚本不阻塞页面。但是,在行为上二者有着重要的区别。

async特性意味着脚本是完全独立的:

  1. 浏览器不会因async脚本而阻塞(与defer类似)。
  2. 其他脚本不会等待async脚本加载完成,同样,async脚本也不会等待其他脚本。
  3. DOMContentLoaded和异步脚本不会彼此等待:
    • DOMContentLoaded可能会发生在异步脚本之前(如果异步脚本在页面完成后才加载完成)
    • DOMContentLoaded也可能发生在异步脚本之后(如果异步脚本很短,或者是从HTTP缓存中加载的) 换句话说,async脚本会在后台加载,并在加载就绪时运行。DOM和其他脚本不会等待它们,它们也不会等待其它的东西。async脚本就是一个会在加载完成时执行的完全独立的脚本。就这么简单,现在明白了吧?

下面是一个类似于我们在讲defer时所看到的例子:long.jssmall.js两个脚本,只是现在defer变成了async

它们不会等待对方。先加载完成的(可能是small.js)—— 先执行:

代码语言:javascript
复制
<p>...content before scripts...</p>

<script>
  document.addEventListener('DOMContentLoaded', () => alert("DOM ready!"));
</script>

<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>

<p>...content after scripts...</p>

页面内容立刻显示出来:加载写有的脚本不会阻塞页面渲染。

DOMContentLoadedasync

较小的脚本排在第二位,但可能会比这个长脚本先加载完成,所以会先执行。虽然,可能是先加载完成,如果它被缓存了的话,那么它就会先执行。换句话说,异步脚本以的顺序执行。当我们将独立的第三方脚本集成到页面时,此时采用异步加载方式是非常棒的:,等,因为它们不依赖于我们的脚本,我们的脚本也不应该等待它们:

原理拆解

按照上面的原理我画了一张图来解释加载顺序和对HTML加载时间的影响。

可以看到,总的HTML加载时间,下载脚本的时间,执行脚本的时间是固定的。不同之处在于HTML阻塞的时间以及执行脚本的次序。

  1. 不加任何asyncdefer的情况,页面总加载时间最长,是 HTML加载时间+下载脚本时间+执行脚本时间
  2. 加了asyncdefer的时间,在加载HTML时间足够长的情况下,所有静态资源总的加载时间都是 HTML加载时间+执行脚本时间

适用内容

然后,必须考虑到页面加载时间和脚本加载顺序,以及各个脚本直接存在的依赖关系。

从参考文档来看,

  1. defer特性除了告诉浏览器不要阻塞页面之外,还可以确保脚本执行的相对顺序。
    • 这个很适合使用到Vue和jquery等js框架的js脚本,给它们添加defer属性以后,可以确保HTML加载完毕,且js下载完毕后,各个js脚本继续按照引入的顺序执行,从而确保不会因为依赖缺失而报错。
  2. 其他脚本不会等待async脚本加载完成,同样,async脚本也不会等待其他脚本。
    • 这个适合使用原生js没有引用任何js框架,自己独立就能运行,且体量相对较小的js脚本,随页面加载同步下载执行。

使用范例

此处以我使用的Butterfly主题中添加的几个js为例。

这里首先说一下我个人的想法,不一定对,本着尽可能减少页面阻塞资源的情况下,我建议:

  1. 尽可能给每个script标签都加上deferasync
  2. 确定为独立脚本或原生脚本的情况下,使用async。这一条并不适用于大型js(例如busuanzi.js)。原因可以看上面的原理拆解图,大型js的执行时间依然会造成大面积的HTML阻塞。
  3. 如果实在不清楚应该添加哪个,则以defer求稳,确保依赖项不会缺失。
  4. 总的来说,async加在那些非必要的,起装饰或者优化效果的js上,defer加在那些确保页面完整性的必要js上。
  5. 来自Heo的建议,不要给影响页面生成的js(例如util.js、main.js、lazy_load.js、vue.js、jquery.js)添加异步加载标签(不论是async还是defer都不要加),不然会造成大面积页面功能模块失效。推测是由于部分HTML元素需要由js动态写入,抑或部分整合在各个pug中的script片段无法添加defer导致。

inject配置项

代码语言:javascript
复制
bottom:
  - <script defer https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
  # Vue.js作为依赖项,必须确保在所有使用到它提供的方法的诸多js之前引入,并且添加defer
  - <script defer src="/live2d-widget/autoload.js"></script>   
  # 看板娘使用到了jquery依赖,所以需要defer
  - <script async src="/js/dytitle.js"></script>
  # 动态网页标题是原生js,且体量小,可以直接async
  - <script async src="/js/mouse_snow.min.js"></script>
  # 鼠标滑动雪花是原生js,且体量小,可以直接async
  - <script defer data-pjax src="/clock/js/clock.js"></script>
  # 首页电子钟用到了vue依赖,所以需要defer
  - <script defer data-pjax src="/magnet/catalogMagnet.js"></script>
  # 首页磁贴用到了vue依赖,所以需要defer
  - <script defer src="/botui/botui.js"></script>
  - <script defer data-pjax src="/botui/botui_init.js"></script>
  # 侧栏聊天窗用到了vue依赖,所以需要defer
  - <script defer src="/runtime/flipcountdown.js"></script>
  - <script defer data-pjax src="/runtime/runtime.js"></script>
  # 页脚计时器用到了jquery依赖,所以需要defer
  - <script defer src="https://cdn.jsdelivr.net/npm/artitalk"></script>
  - <script defer data-pjax src="/artitalk/artitalkkey.js"></script>
  - <script defer data-pjax src="/artitalk/artitalk.js"></script>
  # 侧栏说说依赖于artitalk.js,必须确保其加载顺序,在保证页面引入顺序的同时添加defer
  - <script async src="/js/redirect.js"></script>
  - <script async src="/js/mirror.js"></script>
  # 404和镜像站重定向都是原生js,可以直接async
  - <script defer data-pjax src="https://cdn.jsdelivr.net/gh/Akilarlxh/Akilarlxh.github.io@v3.3.3_3/gitcalendar/js/gitcalendar.js"></script>
  # gitcalendar用到了vue依赖,所以需要defer

CDN配置项

CDN配置项的引入先于inject,至于如何给script标签添加deferasync属性,则要先找到引入位置。 在[Blogroot]\themes\butterfly\layout\includes\additional-js.pug中修改。

代码语言:javascript
复制
div
  script(src=url_for(theme.CDN.jquery))
  script(src=url_for(theme.CDN.utils))
  script(src=url_for(theme.CDN.main))

  if theme.translate && theme.translate.enable
    script(defer src=url_for(theme.CDN.translate))

  if theme.medium_zoom
    script(defer src=url_for(theme.CDN.medium_zoom))
  else if theme.fancybox
    script(defer src=url_for(theme.CDN.fancybox))

  if theme.instantpage
    script(src=url_for(theme.CDN.instantpage) type="module" defer)

  if theme.lazyload.enable
    script(src=url_for(theme.CDN.lazyload))

  if (theme.snackbar && theme.snackbar.enable)
    script(defer src=url_for(theme.CDN.snackbar))

  if theme.pangu && theme.pangu.enable
    !=partial('includes/third-party/pangu.pug', {}, {cache:theme.fragment_cache})

  //- search
  if theme.algolia_search.enable
    script(defer src=url_for(theme.CDN.algolia_js))
  else if theme.local_search.enable
    script(defer src=url_for(theme.CDN.local_search))

拓展内容,CSS的异步加载

页面载入并渲染的流程可以简单理解为以下情况:

加载HTML资源->解析HTML->加载CSS资源,同时构建DOM树->解析CSS,同时渲染DOM树

与js的加载执行过程十分相似,加载CSS时也会造成HTML加载阻塞。

既然js可以异步加载,那CSS理论上应该也可以。虽然不能像js一样直接通过asyncdefer来定义加载顺序那么方便。

目前有效手段有两种,一种是通过定义一个无效media,使得该CSS引入优先级最低,再用onload="this.media='all'"在页面加载完成后纠正media,并加载CSS。

写法如下: 未加入异步加载:

代码语言:javascript
复制
<link rel="stylesheet" href="/example.css">

加入异步加载后:

代码语言:javascript
复制
<link rel="stylesheet" href="/example.css" media="defer" onload="this.media='all'">

注意此处的media=”defer”中的defer并无意义,是个无效media(只是方便理解才这么写),目的是让浏览器认为这个CSS文件优先级非常低,从而在不阻塞的情况下进行加载。

加载完成以后,样式转为对所有设备生效是onload="this.media='all'",但是其实还有其他的设备可供选择。

描述

screen

计算机屏幕(默认)。

tty

电传打字机以及类似的使用等宽字符网格的媒介。

tv

电视机类型设备(低分辨率、有限的滚屏能力)。

projection

放映机。

handheld

手持设备(小屏幕、有限带宽)。

print

打印预览模式/打印页面。

braille

盲人点字法反馈设备。

aural

语音合成器。

all

适用于所有设备。

还有一种利用rel="preload"属性的方案,但是目前只有Chrome浏览器可以完美支持,等推广还需要很长一段时间,写法如下:

代码语言:javascript
复制
<link rel="preload" href="cssfile.css" as="style" onload="this.rel='stylesheet'">

CSS整合

以下内容仅针对butterfly主题。其他主题可以理解原理后进行操作。实际上就是使用@import引入自定义样式。

相信很多小伙伴在看了上述的CSS异步加载方案后,肯定迫不及待的去给自己博客的魔改自定义样式添加异步加载属性了,就算不是,现在也给我演一下,配合我的工作,这么做虽然可以减少HTML页面阻塞,但是很可能会出现首屏页面有那么几秒钟存在大片无样式的板块的情况。

所以我们可以确立一条原则,为了追求视觉体验,不要给index.css等涉及首页样式的CSS文件添加异步加载。

然而事实上,相比于给CSS添加异步加载,不如将我们的魔改样式整合到index.css文件内,减少对服务器的请求次数。这样更能节省加载时间。

我的做法如下:

[Blogroot]\themes\butterfly\source\css\路径下新建_custom文件夹,然后把魔改样式的CSS文件拖动进去。文件目录层级可以表示为以下情况:

[[Blogroot]\themes\butterfly\source\css\index.styl中新增一行代码:@import '_custom/*.css',表示引入_custom文件夹下的所有CSS文件。

如果是使用的外链css,也可以在这里引入。同样是修改 [Blogroot]\themes\butterfly\source\css\index.styl代码,使用@import逐行引入:

这样一来,每个魔改方案的CSS依然可以在独立的CSS文件中找到并修改(如果是手动添加整合的话,只能用注释分割,显然很不利于后续查找修改),而在每次提交时,运行hexo g的过程中就会将所有CSS文件都整合到index.css,可以在主题配置文件的CDN配置项里给index.css加上jsdelivr进一步提升加载速度(注意刷新jsdelivr的缓存)。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在最前
  • 参考内容
  • 原理分析
    • defer
      • async
      • 原理拆解
      • 适用内容
      • 使用范例
        • inject配置项
        • CDN配置项
        • 拓展内容,CSS的异步加载
          • CSS整合
          相关产品与服务
          内容分发网络 CDN
          内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档