JavaScript异步加载问题总结


Posted in Javascript onFebruary 17, 2018

同步加载的问题

默认的js是同步加载的,这里的“加载”可以理解成是解析、执行,而不是“下载”,在最新版本的浏览器中,浏览器对于代码请求的资源都是瀑布式的加载,而不是阻塞式的,但是js的执行总是阻塞的。这会引起什么问题呢?如果我的index页面要加载一些js,但是其中的某个请求迟迟得不到响应,于是阻塞了后面的js代码的执行(同步加载),同时页面渲染也不能继续(如果js引入是在head标签后)。

<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js'></script>
<script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>
this is a test

比如上面的这段代码,保存为index.html文件,页面的主体是一个简单的字符串,但是代码执行后页面迟迟都是空白,为何?因为请求的js迟迟无法加载(可能由于谷歌被墙等原因),于是阻塞了后面的代码的执行,页面得不到渲染。可能你会提议,把js代码放到</body>前不就能先渲染页面了!好方法,我们尝试着将js放后面:

this is a test
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js'></script>
<script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>

页面瞬间被渲染,“this is a test"也很快出现在前台,世界似乎平静了,可是:

this is a test
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js'></script>
<script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>
<script type="text/javascript">
 console.log('hello world');
</script>

在前面代码的基础上简单加了一段代码,但是"hello world"迟迟无法在控制台输出,显然前面的js请求阻塞了后面代码的加载,我们恍然大悟,改变js的加载位置只能改变页面的渲染,然而对于js的加载并没有什么卵用,js还是会阻塞。

实现js异步加载

我们的要求似乎很简单,能在页面加载的同时,在控制台输出字符串即可,再讲的通俗一点,就是在请求第一段谷歌提供的js的同时,继续执行下面的js,也就是实现js的异步加载。

最常见的做法是动态生成script标签:

<body>
 this is a test
 <script type="text/javascript">
  ~function() {
   var s = document.createElement('script');
   s.src = 'http://china-addthis.googlecode.com/svn/trunk/addthis.js';
   document.body.appendChild(s);
  }();
 </script>
 <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>
 <script type="text/javascript">
  console.log('hello world');
 </script>
</body>

但是还是有点问题,这种加载方式在加载执行完之前会阻止 onload 事件的触发,而现在很多页面的代码都在 onload 时还要执行额外的渲染工作等,所以还是会阻塞部分页面的初始化处理:

<body>
 this is a test
 <script type="text/javascript">
  ~function() {
   // function async_load() {
    var s = document.createElement('script');
    s.src = 'http://china-addthis.googlecode.com/svn/trunk/addthis.js';
    document.body.appendChild(s);
   // }
   // window.addEventListener('load', async_load, false);
  }();

  window.onload = function() {
   var txt = document.createTextNode(' hello world');
   document.body.appendChild(txt);
  };
 </script>
 <script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>
</body>

比如上面的代码不能很好地渲染”hello world”,我们只需将注释去掉就可以了,让谷歌提供的js在onload 时才开始异步加载。这样就解决了阻塞 onload 事件触发的问题。

补充DOMContentLoaded 与 OnLoad 事件 DOMContentLoaded : 页面(document)已经解析完成,页面中的dom元素已经可用。但是页面中引用的图片、subframe可能还没有加载完。 OnLoad:页面的所有资源都加载完毕(包括图片)。浏览器的载入进度在这时才停止。这两个时间点将页面加载的timeline分成了三个阶段。

以上似乎能较好解决这个问题,但是html5提供了更简便的方法,async属性!

this is a test
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' async='async'></script>
<script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>
<script type="text/javascript">
 console.log('hello world');
</script>

async是html5的新属性,async 属性规定一旦脚本可用,则会异步执行(一旦下载完毕就会立刻执行)。

需要注意的是async 属性仅适用于外部脚本(只有在使用 src 属性时)

defer属性常常和async一起提起:

this is a test
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' defer='defer'></script>
<script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js'></script>
<script type="text/javascript">
 console.log('hello world');
</script>

似乎实现效果差不多,但是真的一样吗?我们来看看defer属性的定义。

以前的defer只支持ie的hack,现在html5的出现开始全面支持defer。defer 属性规定当页面已完成加载后,才会执行脚本。defer 属性仅适用于外部脚本(只有在使用 src 属性时)。ps:ie支持的defer似乎并非如此,因为对ie无感,不深究,有兴趣的可以去查阅相关资料。

既然async和defer经常一起出现,那么辨析一下吧!

如果没有async和defer属性(赋值为true,下同),那么浏览器会立即执行当前的js脚本,阻塞后面的脚本;如果有async属性,加载和渲染后续文档元素的过程将和当前js的加载与执行并行进行(异步);如果有defer属性,那么加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素(DOM)解析完成之后,DOMContentLoaded 事件触发之前完成。

来看一张网上盗的图:

JavaScript异步加载问题总结

蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

此图告诉我们以下几个要点(摘自defer和async的区别):

  1. defer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
  2. 它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的
  3. 关于 defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用
  4. async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行
  5. 仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics

但是在我看来(以下个人理解,如有出入还望指出),defer在异步加载上的应用并不会比async广。async的英文解释是异步,该属性作用在脚本上,使得脚本加载(下载)完后随即开始执行,和动态插入script标签作用类似(async只支持h5,后者能兼容浏览器);而defer的英文解释是延迟,作用也和字面解释类似,延迟脚本的执行,使得dom元素加载完后才开始有序执行脚本,因为有序,所以会带来另一个问题:

this is a test
<script type="text/javascript" src='http://china-addthis.googlecode.com/svn/trunk/addthis.js' defer='defer'></script>
<script type="text/javascript" src='http://libs.baidu.com/jquery/2.0.0/jquery.min.js' defer='defer'></script>
<script type="text/javascript" src='index.js' defer='defer'></script>
console.log('hello world');

如果执行这段代码,控制台的“hello world”也会迟迟得不到结果。所以我觉得还是async好用,如果要考虑依赖的话,可以选择requirejs、seajs等模块加载器。

总结

JavaScript的异步加载还有一些方式,比如:AJAX eval(使用AJAX得到脚本内容,然后通过eval(xmlhttp.responseText)来运行脚本)、iframe方式等。

以上理解如果有出入,还望指出~感谢大家对三水点靠木的支持。

Javascript 相关文章推荐
浅析tr的隐藏和显示问题
Mar 05 Javascript
Jquery插件实现点击获取验证码后60秒内禁止重新获取
Mar 13 Javascript
JavaScript实现水平进度条拖拽效果
Jan 18 Javascript
JS仿JQuery选择器功能
Mar 08 Javascript
解决Extjs下拉框不显示的问题
Jun 21 Javascript
详解基于 axios 的 Vue 项目 http 请求优化
Sep 04 Javascript
微信小程序实现换肤功能
Mar 14 Javascript
微信小程序制作表格的方法
Feb 14 Javascript
Vue起步(无cli)的啊教程详解
Apr 11 Javascript
小程序异步问题之多个网络请求依次执行并依次收集请求结果
May 05 Javascript
javascript实现前端分页功能
Nov 26 Javascript
JS数组索引检测中的数据类型问题详解
Jan 11 Javascript
js装饰设计模式学习心得
Feb 17 #Javascript
Vue组件库发布到npm详解
Feb 17 #Javascript
JS声明对象时属性名加引号与不加引号的问题及解决方法
Feb 16 #Javascript
JavaScript中严格判断NaN的方法
Feb 16 #Javascript
[原创]js实现保存文本框内容为本地文件兼容IE,chrome,火狐浏览器
Feb 14 #Javascript
jQuery实现鼠标响应式透明度渐变动画效果示例
Feb 13 #jQuery
jQuery实现鼠标响应式淘宝动画效果示例
Feb 13 #jQuery
You might like
php实现的替换敏感字符串类实例
2014/09/22 PHP
PHP使用curl函数发送Post请求的注意事项
2016/11/26 PHP
PHP插件PHPMailer发送邮件功能
2017/02/28 PHP
Yii2 队列 shmilyzxt/yii2-queue 简单概述
2017/08/02 PHP
Yii2框架自定义验证规则操作示例
2019/02/08 PHP
超清晰的document对象详解
2007/02/27 Javascript
用JAVASCRIPT如何给&amp;lt;textarea&amp;gt;&amp;lt;/textarea&amp;gt;赋值
2007/04/20 Javascript
jquery实现兼容浏览器的图片上传本地预览功能
2013/10/14 Javascript
JavaScript判断访问的来源是手机还是电脑,用的哪种浏览器
2013/12/12 Javascript
VueJs路由跳转——vue-router的使用详解
2017/01/10 Javascript
使用veloticy-ui生成文字动画效果
2018/02/08 Javascript
JavaScript生成指定范围随机数和随机序列的方法
2018/05/05 Javascript
如何实现一个webpack模块解析器
2018/10/24 Javascript
Vue编程式跳转的实例代码详解
2019/07/10 Javascript
[01:09]DOTA2次级职业联赛 - 99战队宣传片
2014/12/01 DOTA
Django admin美化插件suit使用示例
2017/12/12 Python
python 读取.csv文件数据到数组(矩阵)的实例讲解
2018/06/14 Python
解决python写入mysql中datetime类型遇到的问题
2018/06/21 Python
Python开发虚拟环境使用virtualenvwrapper的搭建步骤教程图解
2018/09/19 Python
python爬虫 模拟登录人人网过程解析
2019/07/31 Python
python获取全国城市pm2.5、臭氧等空气质量过程解析
2019/10/12 Python
Python使用PyQt5/PySide2编写一个极简的音乐播放器功能
2020/02/07 Python
联想美国官方商城:Lenovo美国
2017/06/19 全球购物
台湾流行服饰购物平台:OB严选
2018/01/21 全球购物
Charles & Colvard官网:美国莫桑石品牌
2019/06/05 全球购物
JDBC操作数据库的基本流程是什么
2014/10/28 面试题
会计学个人自荐信模板
2013/12/13 职场文书
献爱心大型公益活动策划方案
2014/09/15 职场文书
老公婚前保证书
2015/02/28 职场文书
仰望星空观后感
2015/06/10 职场文书
新教师教学工作总结
2015/08/12 职场文书
教师节主题班会教案
2015/08/17 职场文书
2016年教师反腐倡廉心得体会
2016/01/13 职场文书
vue选项卡切换的实现案例
2022/04/11 Vue.js
清空 Oracle 安装记录并重新安装
2022/04/26 Oracle
科学家研发出新型速效酶,可在 24 小时内降解塑料制品
2022/04/29 数码科技