JS文件可以通过来源来分为两个纬度:
第一方JS和第三方JS。第一方JS是网页开发者自己使用的JS代码(内容开发者可控)。而第三方JS则是其他服务提供商提供的(内容开发者不可控),他们将自己的服务包装成JS SDK供网页开发者使用。
从网站开发者的角度来看,第三方JS相比第一方JS有如下几个不同之处:
1 | 1、下载速度不可控 |
如果你的网站上面有很多第三方JS代码,那么“下载速度的不可控”很有可能导致你的网站会被拖慢。因为JS在执行的时候会影响到页面的DOM和样式等情况。浏览器在解析渲染HTML的时候,如果解析到需要下载文件的script标签,那么会停止解析接下来的HTML,然后下载外链JS文件并执行。等JS执行完毕之后才会继续解析剩下的HTML。这就是所谓的HTML解析被阻止。
第三方JS代码并不受网站开发者的控制,很有可能会出现加载时间长甚至加载失败的情况。这时候就会导致整个页面的加载速度变慢。第三方JS代码越多这种风险越大。按照互联网守则:
1 | 网站加载速度越慢,用户流失越多 |
所以要考虑下如何在有很多第三方JS的情况下,保证他们不影响到网站自己的加载速度。可以异步加载这些第三方JS代码。
异步加载
异步加载JS的方法很多,最常见的就是动态创建一个script标签,然后设置其src和async属性,再插入到页面中。这里有个DEMO。实际操作的代码如下:1
2
3
4
5
6
7
8
9
10
11
12<script>
function loadScript(url) {
var xiao = document.getElementsByTagName('script');
var fangkuai = xiao[xiao.length - 1];
var scri = document.createElement('script');
scri.src = url;
scri.async = true;
fangkuai.parentNode.insertBefore(scri, fangkuai);
}
loadScript('XXX.js');
</script>
为了避免IE8以前版本的bug,并且确保script能插入DOM树,所以没有直接document.body.append(src),而是用了insertBefore方法。
改成异步加载第三方JS代码之后,在JS的下载过程中浏览器会继续解析渲染HTML。
因为loadScript的操作也是使用JS实现的,所以在JS下载之前会有一段执行JS代码的消耗。但是这段JS代码很简单,很快就会执行完毕。
除了动态创建script标签的方法,异步加载JS的方法还有很多其他奇技淫巧:
1 | 1、先下载再执行 - 通过XMLHttpReqeust对象或者JSONP方法下载可执行的JS文件,然后使用eval()或者script标签执行JS。第三方JS文件一般是不同域名的且JS内容不可控。 |
浏览器预加载
动态创建script标签的方法可以异步加载第三方JS,但它也有缺陷。如果加载代码之前有外链JS文件或CSS文件需要下载1
2
3
4
5
6
7
8
9
10
11
12
13
14<script src="app1.js"></script>
<script src="app2.js"></script>
<script>
function loadScript(url) {
var xiao = document.getElementsByTagName('script');
var fangkuai = xiao[xiao.length - 1];
var scri = document.createElement('script');
scri.src = url;
scri.async = true;
fangkuai.parentNode.insertBefore(scri, fangkuai);
}
loadScript('XXX.js');
</script>
那么会先下载解析app1.js和app2.js再执行我们的loadScript方法,所以第三方脚本的下载也会被暂停。
为了利用预加载这个特性,可以使用如下的写法:
1 | <script src="app1.js"></script> |
使用标准的script标签写法,确保浏览器能够正确的识别这是一个外链JS文件。同时设置async标签,浏览器便会异步加载XXX.js文件,不会暂停掉浏览器的解析执行。
一些旧浏览器并不支持async属性。这会导致这个XXX.js文件在这些浏览器中不是异步的,并且会阻止掉页面渲染。移动浏览器大多都支持async标签。
当然如果你不介意第三方JS代码(本身也支持支持)被延后到页面解析完毕后执行,可以再加上defer属性:
1 | <script src="./XXX.js" async defer></script> |
Firefox支持defer属性要比支持async早一点点。当浏览器同时使用了async和defer属性之后,浏览器会忽略defer属性。所以可以放心的同时使用async和defer属性。对于不支持async的浏览器,会自动fallback到defer。不过支持程度也就多了一点点,Firefox的旧版占比已经很低了,基本可以忽略不计。
页面onload事件
1 | 上面提到的两种方法还有一个缺点:会影响到页面的onload事件。这对第一方JS可能没有影响,因为第一方JS大都是页面主要逻辑,从业务逻辑上来说它们的加载影响到页面onload事件触发不会有问题。但第三方JS则不一样,曾经因为Google被墙GA(Google Analytics简称)的加载就会特别慢甚至失败。导致了很多使用了GA的页面加载特别"慢",页面一直处于loading状态。大家先通过fiddler代理来设置XXX.js的加载时间为10秒,然后打开之前的DEMO,查看页面的loading是否会被延长。 |
Friendly IFrame方法
1 | (function(url){ |
这样加载Javascript,就不会阻止浏览器的onload事件,提升普通用户的体验。还有另一个好处:第三方的Javascript代码在独立的iframe中运行,不会与主页面中的JS相互干扰。已经有了一些基于这个想法的开源实现,例如:lightning.js是一个专用于快速、安全、异步地加载第三方JS代码的库。
这个方法也不完美,它需要创建一个iframe标签导致了开销较大。同时还需要第三方JS本身的支持。第三方JS代码运行在iframe中,导致它无法获取到页面上的信息。虽然它并非跨域可以获得window.parent,但是第三方代码并不能知道自己是否在iframe中,需要在加载第三方JS代码的时候通知它。具体的通知方法千变万化,而第三方JS的内容又不受我们控制。