现流行浏览器对于静态资源的预加载
传统的浏览器,对于静态资源加载,会阻塞 HTML 解析器的线程进行,无论内联还是外链。
例如:
<script src="test1.js"></script>
<script src="test2.js"></script>
<script src="test3.js"></script>
<img src="img.png" />
传统浏览器HTML解析器,会从test1.js 逐一解析到img.png,只个解析过程是同步的,只有当test1.js解析加载完成才会到test2.js 顺序加载。假设js文件加载时间需要1秒,img文件也需要1秒的时间,那么除去页面其他阶段的render时间不计,img图片会是4秒之后才显示给用户。
然而,现在主流浏览器对于静态资源的解析,已经做到了预解析和预加载。
相比传统浏览器,当浏览器HTML解析器,遇到test1.js静态资源的是,主线程中的解析器暂停解析,浏览器会新开启一个解析器线程,去预加载后面的资源。假设js文件加载时间需要1秒,img文件也需要1秒的时间。当浏览器遇到第一个js文件test1.js的时候,会新开启一个解析器线程,去预加载解析其他静态资源。除去页面其他阶段的render时间不计,那么img图片只会是2秒之后才显示给用户。
但浏览器能做的仅仅是预解析和预加载,脚本的执行和 DOM 树的构建仍然必须是线性的
document.write() 打破浏览器的预解析和预加载,因为document.write()加载的JS文件无法让HTML预解析器发现
<script src="test1.js"></script>
<script>
document.write('<script src="http://xxx.com/test2.js"><\/script>')
</script>
<script src="test3.js"></script>
这个例子中,由于 test2.js 是通过 JS 代码插入的,HTML 预解析器是看不到的,所以只有当 test1.js 下载并执行完毕,且第二个内联的 script 执行完毕后,test2.js 才会开始下载,也就是说,test2.js 不能和 test1.js 及 test3.js 并行下载了,从而导致页面展现变慢,同样假设每个文件的下载时间都是 1 秒,那么这三个文件下载执行完就需要两秒,就因为 test2.js 不能预加载。在一个外链的 JS 文件比如 a.js 中执行 document.write("<script...) 也是类似的效果。
针对document.write() 去加载静态资源,我们可以做出什么优化?
将资源转成外链的方式加载。
如下
<script src="test1.js"></script>
<script>
document.write('<script src="http://xxx.com/test2.js"><\/script>')
</script>
<script src="test3.js"></script>
改成
<script src="test1.js"></script>
<script src="test2.js"></script>
<script src="test3.js"></script>
尽可能,让浏览器预解析器发现静态资源文件。但是这并不意味着页面的加载时间会大大减少。
假设test1.js的加载时间为1秒,test2.js的加载时间为10秒。即便test1.js之后的静态资源参与了浏览器的预解析加载,为了配合这句话“但浏览器能做的仅仅是预解析和预加载,脚本的执行和 DOM 树的构建仍然必须是线性的
”。页面始终会全部资源加载完之后才完成渲染,test2.js的10秒加载时间仍旧会让页面处于loading转圈状态。
对于非第三方的静态资源的加载时间太长,应考虑前端资源的优化,这里列出来可能多的优化方案,但是暂不做详解
- 减少静态文件的文件大小 (代码压缩)
- 减少静态文件请求数量 (文件合并)
- gzip
- CDN和缓存
对于第三方的静态资源文件,可以使用async实现异步加载
async 加载静态资源
一句话概括便是异步的 script 根本不会阻塞 HTML 解析器,也就用不到预解析了
目前大部分第三方库都会支持async 异步 加载JS资源,然后去调用一个全局function 例如
<script src="test.js?callback=dosomething" async ></script>
async 的js资源,如果长时间pedding 会影响onload加载时间
关于document.write()的其他知识点
ducument.write 在onload之前,插入执行document.write()都会给页面插入内容,页面onload完成之后,浏览器输出流自动关闭;在此之后,任何一个对当前页面进行操作的document.write()方法将打开—个新的输出流
如下
结果是:
页面onload完成之后,调用document.write
结果是: