记得我刚毕业那会,前端还是jQuery的天下,没有什么项目是一个jQuery搞不定的,如果有,那就再加个插件。

后来慢慢出现了一些前端模块化工具,直至seajs的出现,把模块化推向了一个高潮,期间听说了gmail前端有44万行的JS代码,也是非常震惊。

现在的前端为了更好的用户体验,更快地适应业务,逻辑正在变得越来越复杂,同时也带来越来越多的问题。

体积臃肿的前端

使用webpack的同学对下面这张图很熟悉了,一般我们用webpack打出来的包,都会包含一大堆东西,node_modules文件内容甚至能占到90%以上。

冰山效应

很多时候,用户打开一个页面,同时也加载了很多在这个页面上并不会运行的代码,甚至是永远都不会运行的代码,页面上的这些代码就像海洋里的冰山,运行的只是海平面上很小一部分,很大一部分在海平面下的代码,仅仅只会拖慢我们首页的打开时间。

按需加载

从有模块化起,貌似就有了按需加载功能,很多工具都提供了按需加载的功能,以webpack为例:

1
const jQuery = await import( 'jQuery' );
 
const model = await import( './model/data.js' );

webpack内置函数import很容易就可以实现按需加载的功能,看起来似乎问题就已经解了?然而这只是问题的第一步,仅仅有了按需加载的能力,问题也会随之而来。

预加载

按需加载必然会带来一次网络请求,假设一个场景:

1
用户点击一个按钮,然后跳出一个弹框。

普通情况下,点击按钮之后发生的事情如下:

1
点击按钮(ms) -> 渲染弹框(ms)

如果我们把弹框做按需加载,那么点击按钮之后发生的事情如下:

1
点击按钮(ms) -> 加载弹框代码(s) -> 渲染弹框(ms)

中间多了一个步骤,并且是按秒计算的从网络加载代码,点击一个按钮之后需要等待一段时间,才会弹出弹框,用户操作非常不流畅,如果存在大量这种场景,体验或许还不如不用按需加载。

有什么办法能解这个问题么?一种方案是:预加载,请看下图:

预加载步骤

  1. 首先页面上收集用户的点击行为日志。
  2. 拿到点击行为日志,得出用户最常用的操作路径,这部分数据在静态打包的时候注入到前端。
  3. 每个人的操作路径会有区别,这里可以在前端也记录一份当前用户的点击数据,这里很多时候虽然本地都已经有缓存了,不过提前把缓存加载到内存,也可以节省很多时间。
  4. 在浏览器空闲时,按照第二步和第三步的数据计算权重,最终决定加载顺序。
  5. 同时,前端可以根据鼠标运动轨迹,实时地来预测下一步可能会加载的内容,作为一个旁路辅助。这里目前并没有想到一个很好的办法。

缓存

鉴于目前前端代码普遍存在于CDN的情况,按需加载带来的另外一个非常有优势的地方在于CDN缓存,假设我们有一份jQuery,有两个文件分别依赖了jQuery,情况如下:

a.js

1
var jQuery = function(){ /*jquery代码*/ }

jQuery.query( 'xxx' );

b.js

1
var jQuery = function(){ /*jquery代码*/ }

jQuery.query( 'xxx' );

用户在打开A页面和B页面的时候,分别需要下载a.jsb.js,这时候重复下载了两份jQuery,即使他们是一样的,同时在用户的磁盘,也缓存了两份jQuery

按需加载后的CDN缓存

那么如果我们用按需加载的方式呢?

http://cdn.com/jquery.js

1
var jQuery = function(){ /*jquery代码*/ }

a.js

1
var jQuery = import( 'http://cdn.com/jquery.js' );

jQuery.query( 'xxx' );

b.js

1
var jQuery = import( 'http://cdn.com/jquery.js' );

jQuery.query( 'xxx' );

jQuery.js只会被下载一次,所有页面如果用的都是一个cdn地址,那么只要用户打开过任何一个页面,之后的页面即使用户从未打开过,jQuery也已经被缓存,这带来的是一个1+1>2的效果。

理想中的按需加载

最后说一说我理想中的按需加载,看下图:

  1. 逻辑层和展示层完全剥离。
  2. 用户操作一个页面的顺序,一定是:
    1. 眼睛看到展示层,发现有一个按钮。
    2. 大脑告诉你,这个是一个按钮,是可以点击的。
    3. 用手控制鼠标去点击按钮。
    4. 执行点击按钮之后事情。
  3. 首屏加载的时候,我们可以只加载展示层,逻辑层代码一定是在用户点击按钮之后才会执行。
  4. 首屏加载完成,利用用户看页面,并点击按钮的时间,预加载逻辑层代码,实现最快速的首屏展示。