html外部资源加载的better practice
1. js部分
原则
默认js加载行为堵塞页面渲染(render blocking),指定async/defer
属性的js加载不堵塞,而js执行一定堵塞。
so
- 为避免js加载执行堵塞页面渲染,一般的js脚本应放在
<body />
后(当然一定要在页面渲染前执行的js脚本就没办法了)。 - 重要的js该怎样加载就怎样加载。可隔离出来的第三方js,若有多个顺序依赖,使用defer;否则使用async。
异常情况:script async/defer不兼容
背景:我在ios上访问自己的博客,发现会白屏,显然渲染被卡住了。博客中仅有评论脚本xx.disqus.com
是需要vpn的资源,并且已开启了async
异步加载。
结论:经过数次测试,发现script的async/defer
在ios的浏览器上都无法生效(仅对我测试时的情况而言)。而pc和安卓上表现正常。
解决:用document.createScript
的方式在setTimeout中异步插入该脚本。
setTimeout(()\=>{
(function(){
var dsq = document.createElement('script');
dsq.type = 'text/javascript';
dsq.src = 'xxx.js';
document.getElementsByTagName('body')\[0\].appendChild(dsq);
}());
}, 5)
注意:如果不异步,ios上处理该条脚本的方式,会和它天然就在html中一样,卡住html渲染(虽然异步时间是我估摸着填的…如果写0,依旧会被认为内嵌在html中)。
2. css部分
原则
因为渲染树由DOM和CSSOM组成,即css是构建渲染树的必须部分,所以默认css外部加载行为会堵塞页面渲染。
so…
- css放头部。
- 内联是个好习惯。
- 可使用“媒体类型”和“媒体查询”来选择下载部分css。如:
<link href\="style.css" rel\="stylesheet"\>
<link href\="print.css" rel\="stylesheet" media\="print"\>
<link href\="other.css" rel\="stylesheet" media\="(min-width: 40em)"\>
- 进阶部分,比如http2主动推送,以后可继续探索。
3. 图片部分
原则
默认图片加载不堵塞页面渲染。但是如果在首次加载的html里,会堵塞window.onload
行为。
so…
不必太过紧张,图片太多/太大的话,都可按懒加载处理。除了常用的页面滚到对应地方再渲染整个img节点以外;还可用先渲染img节点,但滚动到视野区再赋予src
的方式:
<img data-lazysrc\='http://www.amazingjokes.com/images/20140902-amazingjokes-title.png'/>
function ReLoadImages(){
$('img\[data-lazysrc\]').each( function(){
//\* set the img src from data-src
$( this ).attr( 'src', $( this ).attr( 'data-lazysrc' ) );
}
);
}
4. 浏览器的loading icon停不下来
还有一个我觉得十分重要,但是常常被忽略的页面加载指标——浏览器的loading icon!
作为使用者,常常会有这样的认知:“icon没停下来 === 页面没加载完 === 一动也不敢动”。但其实呢,如果页面上首次渲染的html内任何一个外部资源(比如不绝对重要的第三方资源、某张图片etc)加载pending,浏览器都的加载icon都会卡在那,给人造成误解。
而对开发者而言,window.onload
钩子执行时间也会因此延后!如果用户手动停止了loading icon中止加载,window.onload
就永远也不会执行了(当然,正常情况下不该用这个钩子)。
so…
- 整理好首次渲染的html里必须需要的资源。所有其他资源,特别是有风险的,都可走异步加载(或者改为异步插入更准确一些)。
就拿常用评论插件xx.disqus.com
这个被墙例子而言,哪怕开启了async
选项,不会影响页面渲染;但在请求xx.disqus.com
未超时之前,该页从严格意义上来讲都不算加载完毕,浏览器的loading icon也不会停。所以在存在不稳定access的第三方插件的情况下,可以在setTimeout内动态插入js引用。
总结
一句话而言,不用恪守着“css放头部、js放尾部”之类的条约(虽然它们大多数情况下也是正确的)。多想想为什么要这么放的原因,js的执行是大头,css的加载是大头,而所有的共通目的都是为了不影响dom树的构建啦。
随着http的发展和浏览器的不断提升,比如对多个小资源的同时请求呀,处理效率一定会越来越高。死守着某个时代的固定做法没有意义,就…常保持更新吧。