最近在做个人项目中登陆界面的时候,需要加一个人机验证也就是验证码的功能,和朋友商量再三由于时间有限,便选择引入第三方的验证码。使用过程中发现,该第三方验证码的实现逻辑基本是先编写指定id的HTML标签,然后加载该第三方的js,由其自己渲染而成,因此借这个机会,再次学习总结一下传统的<script>标签加载 JavaScript 脚本相关知识,主要是defer,async属性。
HTML页面解析过程
为了更好的理解脚本加载的知识,首先简单了解一下HTML页面处理过程:(关于解析,请看下文script的内容)
- 浏览器通过HTTP协议请求服务器,获取HMTL文档并开始从上到下解析,构建DOM;
- 在构建DOM过程中,如果遇到外联的样式声明和脚本声明,则暂停文档渲染,创建新的网络连接,并开始下载样式文件和脚本文件;
- 样式文件下载完成后,构建CSSDOM;脚本文件下载完成后,解释并执行,然后继续构建DOM
- 完成文档解析后,将DOM和CSSDOM进行关联和映射,最后将视图渲染到浏览器窗口
在这个过程中,普通脚本文件的下载和执行会阻塞文档的渲染,如果控制得不好,在用户体验上就会造成一定程度的影响
因此,大多提倡将脚本加载放在页面末尾,一般是</body>处:
雅虎军规第18条:把脚本放在底部
脚本加载实例:
<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
// module code
</script>
<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/my.js">
</script>
普通脚本
没有 defer 或 async修饰,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签后面的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。
如果解析遇到多个<script>标签,依次加载顺序执行。
*此操作会阻止后续文档元素的解析和渲染,但是这里有一个预解析的概念
(Webkit 和 Firefox 都进行了这项优化。在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。)*
defer脚本
如果script标签设置了该属性,则浏览器会异步的下载该文件并且不会影响到后续DOM的渲染;
如果有多个设置了defer的script标签存在,则会按照顺序执行所有的script;
defer脚本会在文档渲染完毕后,DOMContentLoaded事件调用前执行。
注意:
*在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在 DOMContentLoaded事件触发前执行,因此最好 只包含一个延迟脚本。 泽卡斯(Zakas. Nicholas C.). JavaScript高级程序设计(第3版) (图灵程序设计丛书)
async脚本
async的设置,会使得script脚本异步的加载并在允许的情况下执行,也就是说加载和渲染后续文档元素的过程将和script加载并行进行
async的执行,并不会按着script在页面中的顺序来执行,而是谁先加载完谁执行。
上述三种总结为一张图片(出处见参考)
关于项目中的应用
由于是模块化开发,在此采用的是再模块内通过动态方式加载第三方的验证码js,主要代码如下。
function load (el, src, callback) {
if (!src) {
return;
}
// _verifyExist(src);
let scriptHeat = document.createElement('script');
scriptHeat.type = 'text/javascript';
scriptHeat.src = src;
scriptHeat.defer = true;
/* 为保证兼容性,在此对回调包装, */
isFunction(callback) && addOnloadHandler(scriptHeat, callback);
el.appendChild(scriptHeat);
}
function isFunction (fn) {
return Object.prototype.toString.call(fn) === '[object Function]';
}
function addOnloadHandler (el, callback) {
el.onload = el.onreadystatechange = function () {
if (!this.readyState || // 这是FF的判断语句,因为ff下没有readyState这人值,IE的readyState肯定有值
this.readyState === 'loaded' || this.readyState === 'complete' // 这是IE的判断语句
) {
callback();
}
};
}
为保证该第三方库执行时有其渲染的元素,所以设置为defer。可能引发问题是如果网络慢或其他原因会导致该验证控件呈现较慢(暂时未遇到),所以项目中也加了遮罩处理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。