首页 首页 物联网 物联网技术 查看内容

加载第三方 JS 的各种姿势

木马童年 2019-1-13 22:10 33 0

网页中加载JS文件是一个老问题了,已经被讨论了一遍又一遍,这里不会再赘述各种经典的解决方案。JS文件可以通过来源来分为两个纬度:第一方JS和第三方JS。第一方JS是网页开发者自己使用的JS代码(内容开发者可控)。而 ...

网页中加载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文件下载执行完毕之后才开始下载。如下图:

虽然这对页面原有JS的执行不会有大的影响,但会影响到第三方JS代码本身的下载与执行。如何解决这个问题呢?

你可能已经发现上面的例子有个问题:HTML代码中 g.js 的位置在 test.js 之后却先下载了。其实这得益于浏览器的预解析机制,会先对HTML代码做静态分析找到外链的JS和CSS文件,然后并行下载下来(但是执行顺序不变)。IE>=8 及其他主流浏览器基本都实现了这个功能。所以在这些支持预先载的浏览器中流程图应该是这样的:

为了利用预加载这个特性,我们可以使用如下的写法:

使用标准的 script 标签写法,确保浏览器能够正确的识别这是一个外链JS文件。同时设置 async 标签,浏览器便会异步加载 test.js 文件,不会暂停掉浏览器的解析执行。流程图如下:

这里有一个 DEMO 。

但它也并不完美,因为一些 旧浏览器 并不支持 async 属性。这会导致这个 test.js 文件在这些浏览器中不是异步的,并且会阻止掉页面渲染。有一个好消息是移动浏览器大多都支持 async 标签,如果你的用户大都是移动浏览器的,或者你的产品不支持旧浏览器,那么你可以使用这种方法。

当然如果你不介意第三方JS代码(本身也支持支持)被延后到页面解析完毕后执行,那么你可以再加上 defer 属性:

Firefox支持 defer 属性要比支持 async 早一点点。而且当浏览器同时使用了 async 和 defer 属性之后, 浏览器会忽略 defer 属性 。所以可以放心的同时使用 async 和 defer 属性。对于不支持 async 的浏览器,会自动fallback到 defer 。不过支持程度也就多了一点点,Firefox的旧版占比已经很低了,基本可以忽略不计。

页面 onload 事件

上面提到的两种方法还有一个缺点:会影响到页面的 onload 事件。这对第一方JS可能没有影响,因为第一方JS大都是页面主要逻辑,从业务逻辑上来说它们的加载影响到页面 onload 事件触发不会有问题。但第三方JS则不一样,曾经因为Google被墙GA(Google Analytics简称)的加载就会特别慢甚至失败。导致了很多使用了GA的页面加载特别"慢",页面一直处于loading状态。大家先通过fiddler代理来设置 test.js 的加载时间为10秒,然后打开之前的DEMO,查看页面的loading是否会被延长。下面是我打开第一个 DEMO 的结果:

可以看到因为 test.js 的下载速度变慢,整个页面一直处于loading状态。页面的 load 事件要等到全部加载完成之后才会触发。如果页面中的主要逻辑是在页面 load之后再执行,那么页面很可能会在很长一段时间内不可用。极大的影响了用户的使用体验。

Friendly IFrame方法

为了解决这个问题,meebo的工程师想了一个方案来解决这个问题:

(function(url){

// 第一部分

var dom,doc,where,iframe = document.createElement('iframe');

iframe.src = "javascript:false";

iframe.title = ""; iframe.role="presentation";

(iframe.frameElement || iframe).style.cssText = "width: 0; height: 0; border: 0";

where = document.getElementsByTagName('script');

where = where[where.length - 1];

where.parentNode.insertBefore(iframe, where);

// 第二部分

try {

doc = iframe.contentWindow.document;

} catch(e) {

// IE下如果主页面修改过document.domain,那么访问用js创建的匿名iframe会发生跨域问题,必须通过js伪协议修改iframe内部的domain

dom = document.domain;

iframe.src="javascript:var d=document.open();d.domain='"+dom+"';void(0);";

doc = iframe.contentWindow.document;

}

doc.open()._l = function() {

var js = this.createElement("script");

if(dom) this.domain = dom;

js.id = "js-iframe-async";

js.src = url;

this.body.appendChild(js);

};

doc.write('');

doc.close();

})('test.js');

上述代码分为两个部分:

创建了一个隐藏的 iframe 标签,设置其 src 值为JS代码,然后插入到主页面中

在 iframe 标签load之后加载JS脚本

这样加载Javascript,就不会阻止浏览器的 onload 事件,提升普通用户的体验。还有另一个好处:第三方的Javascript代码在独立的iframe中运行,不会与主页面中的JS相互干扰。已经有了一些基于这个想法的开源实现,例如:lightning.js是一个专用于快速、安全、异步地加载第三方JS代码的库。

这个方法也不完美,它需要创建一个 iframe 标签导致了开销较大。同时还需要第三方JS本身的支持。第三方JS代码运行在iframe中,导致它无法获取到页面上的信息。虽然它并非跨域可以获得 window.parent ,但是第三方代码并不能知道自己是否在iframe中,需要在加载第三方JS代码的时候通知它。具体的通知方法千变万化,而第三方JS的内容又不受我们控制。

富媒体广告JS(用于展示交互广告的JS)一般都会运行在隔离环境里面,且不需要(不允许)访问外部的window对象。如果你需要加载的第三方JS全部是广告时,那么使用这个方案是OK的,否则并不是最为合适。幸运的是有一个叫 iAB(The Interactive Advertising Bureau,简称iAB) 的组织,建立了一套 工业级标准 。虽然标准已经比较旧了,但是里面提到了通过设置变量 inDapIF 为 true 来通知第三方JS:你现在正运行在iframe中。因为iAB成员较多影响力大,所以遵循这个标准是有好处的,比起自己玩一套要好的多。

总结

方法DEMO异步预下载阻止onload事件比较
动态创建script标签 dynamic_script.html 是(IE<=9除外) 兼容性最好、普适性最高的方案
async_script.html IE>=10及其他主流浏览器可以 如果你的用户没有IE<10(或者偏移动端),那么这是最合适的
async_defer.html 如果不介意IE<10中JS的执行会被延后到文档解析完毕,那么这是最合适的方案
Friendly Iframe friendly_iframe.html 投放代码太过复杂,且需要第三方JS的支持。比较适用于广告的加载,因为广告通常在隔离环境中即可,不需要访问外部window

在不久的将来,多智时代一定会彻底走入我们的生活,有兴趣入行未来前沿产业的朋友,可以收藏多智时代,及时获取人工智能、大数据、云计算和物联网的前沿资讯和基础知识,让我们一起携手,引领人工智能的未来!

解决方案 服务提供商 互联网 工程师
0
为您推荐
仿真优化 5G 和物联网的天线设计

仿真优化 5G 和物联网的天线设计

5G 移动网络和物联网(Internet of Things,简称 IoT)是射频及微波行业的两大热点话题…...

基于物联网技术的消防器材管理系统研究

基于物联网技术的消防器材管理系统研究

本文描述了一种基于物联网技术的消防器材管理系统(后文简称 “本系统”),详细说明了…...

利用热能收集延长远程传感器所用电池的寿命

利用热能收集延长远程传感器所用电池的寿命

人们常常在周围充满能源的环境中看到无线和有线传感器系统,这种环境能源非常适合用来…...

传感器设计意识

传感器设计意识

金属面板电容(MoC)触摸系统的一大优点在于其传感器的灵活性。这也就是说,其传感器设…...

通过智能无源传感器,实现监测温度、湿度或压力

通过智能无源传感器,实现监测温度、湿度或

在任何给定时间内,物联网 (IoT) 中大多数设备都可能处于空闲状态。通常,仅需要 IoT …...

基于STM32智能家居系统的设计与实现

基于STM32智能家居系统的设计与实现

智能家居(又称智能住宅)是以住宅为平台,兼备建筑、网络通信、信息家电、设备自动化,…...