网页中加载JS文件是一个老问题了,已经被讨论了一遍又一遍,这里不会再赘述各种经典的解决方案。JS文件可以通过来源来分为两个纬度:第一方JS和第三方JS。第一方JS是网页开发者自己使用的JS代码(内容开发者可控)。而第三方JS则是其他服务提供商提供的(内容开发者不可控),他们将自己的服务包装成JS SDK供网页开发者使用。这篇文章关注的第三方JS文件的加载。
从网站开发者的角度来看,第三方JS相比第一方JS有如下几个不同之处:
下载速度不可控
JS地址域名与网站域名不同
文件内容不可控
不一定有强缓存(Cache-Control/Expires)
如果你的网站上面有很多第三方JS代码,那么“下载速度的不可控”很有可能导致你的网站会被拖慢。因为JS在执行的时候会影响到页面的DOM和样式等情况。浏览器在解析渲染HTML的时候,如果解析到需要下载文件的 script 标签,那么会停止解析接下来的HTML,然后下载外链JS文件并执行。等JS执行完毕之后才会继续解析剩下的HTML。这就是所谓的『HTML解析被阻止』。浏览器解析渲染页面的抽象流程图如下:
第三方JS代码并不受网站开发者的控制,很有可能会出现加载时间长甚至加载失败的情况。这时候就会导致整个页面的加载速度变慢。第三方JS代码越多这种风险越大。按照互联网守则:
网站加载速度越慢,用户流失越多
所以要考虑下如何在有很多第三方JS的情况下,保证他们不影响到网站自己的加载速度。我们可以异步加载这些第三方JS代码。
异步加载
异步加载JS的方法很多,最常见的就是动态创建一个 script 标签,然后设置其 src 和 async 属性,再插入到页面中。这里有个 DEMO 。实际操作的代码如下:
PS:为了避免 IE8以前版本的bug ,并且确保script能插入DOM树,所以这里没有直接 document.body.append(src) ,而是调用了 insertBefore 方法。
改成异步加载第三方JS代码之后,在JS的下载过程中浏览器会继续解析渲染HTML。流程图就变成了如下:
因为 loadScript 的操作也是使用JS实现的,所以在JS下载之前会有一段执行JS代码的消耗。但是这段JS代码很简单,很快就会执行完毕。
除了动态创建 script 标签的方法,异步加载JS的方法还有很多其他奇技淫巧,这里也罗列了一下:
先下载再执行 - 通过 XMLHttpReqeust 对象或者 JSONP 方法下载可执行的JS文件,然后使用 eval() 或者 script 标签执行JS。第三方JS文件一般是不同域名的且JS内容不可控,所以此方法就不适用了
iframe 中加载JS – 将你的JS文件直接放到另一个页面的HTML中,然后将此页面URL地址作为 iframe 标签 src 属性。此方法需要增加一次页面请求,而且因为是在 iframe 内部执行了,第三方JS文件本身也需要修改,故并不是很适用
先缓存再执行 – 利用JS文件的强缓存,先使用 new Image().src = 'http://url.com/sample.js' 之类(或者 Object 对象)的 方法 下载JS文件。然后在真正需要解析执行JS的时候下载(有缓存,不必再次下载)和执行JS文件。此方法不仅仅适用于JS文件,同样也可以用于CSS文件。这样我们就可以将静态文件的下载和解析执行(使用)分开,批量并行下载,然后在合适的机会解析执行(使用)。但此方法需要强缓存的配合,第三方JS为了在版本发布时更早的更新JS代码一般都不会设置缓存,甚至有些第三方JS的代码是服务器端动态生成的。所以也不是适用于第三方JS。
浏览器预加载机制
动态创建 script 标签的方法可以异步加载第三方JS,但它也有缺陷。如果加载代码之前有外链JS文件或CSS文件需要下载,如下面的代码:
那么会先下载解析 app1.js 和 app2.js 再执行我们的 loadScript 方法,所以第三方脚本的下载也会被暂停。流程图如下:
而如今我们页面中代码如此复杂,触发这种case的情况很多。上面的 DEMO 中实际下载过程也确实是这样,动态创建 script 标签方式下载的test.js需要等到其他CSS和JS文件下载执行完毕之后才开始下载。如下图: