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 相关文章推荐
jquery插件validate验证的小例子
May 08 Javascript
jquery 追加tr和删除tr示例代码
Sep 12 Javascript
jQuery计算textarea中文字数(剩余个数)的小程序
Nov 28 Javascript
JS小功能(操作Table--动态添加删除表格及数据)实现代码
Nov 28 Javascript
JS获取复选框的值,并传递到后台的实现方法
May 30 Javascript
JQuery validate 验证一个单独的表单元素实例
Feb 17 Javascript
javascript按钮禁用和启用的效果实例代码
Oct 29 Javascript
React组件refs的使用详解
Feb 09 Javascript
vue项目中公用footer组件底部位置的适配问题
May 10 Javascript
通过扫小程序码实现网站登陆功能
Aug 22 Javascript
uni app仿微信顶部导航条功能
Sep 17 Javascript
vue实现商品列表的添加删除实例讲解
May 14 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快速url重写更新版[需php 5.30以上]
2010/04/25 PHP
thinkphp常见路径用法分析
2014/12/02 PHP
php实现xml与json之间的相互转换功能实例
2016/07/07 PHP
php array_chunk()函数用法与注意事项
2019/07/12 PHP
laravel5.6实现数值转换
2019/10/23 PHP
php实现JWT验证的实例教程
2020/11/26 PHP
Hutia 的 JS 代码集
2006/10/24 Javascript
javascript 清除输入框中的数据
2009/04/13 Javascript
Extjs Ajax 乱码问题解决方案
2009/04/15 Javascript
javascript中返回顶部按钮的实现
2015/05/05 Javascript
理解JavaScript事件对象
2016/01/25 Javascript
bootstrap实现每隔5秒自动轮播效果
2016/12/20 Javascript
Angular 4环境准备与Angular cli创建项目详解
2017/05/27 Javascript
使用async、enterproxy控制并发数量的方法详解
2018/01/02 Javascript
vue.js做一个简单的编辑菜谱功能
2018/05/08 Javascript
Vue2.0 v-for filter列表过滤功能的实现
2018/09/07 Javascript
javascript实现5秒倒计时并跳转功能
2019/06/20 Javascript
微信小程序实现Swiper轮播图效果
2019/11/22 Javascript
前端使用crypto.js进行加密的函数代码
2020/08/16 Javascript
vue elementui tree 任意级别拖拽功能代码
2020/08/31 Javascript
vue中的计算属性和侦听属性
2020/11/06 Javascript
VueCli生产环境打包部署跨域失败的解决
2020/11/13 Javascript
python实现登陆知乎获得个人收藏并保存为word文件
2015/03/16 Python
使用Nginx+uWsgi实现Python的Django框架站点动静分离
2016/03/21 Python
python监控进程脚本
2018/04/12 Python
利用Python如何将数据写到CSV文件中
2018/06/05 Python
python实现函数极小值
2019/07/10 Python
css3一个简易的 LED 数字时钟实现方法
2020/01/15 HTML / CSS
html5使用canvas绘制一张图片
2014/12/15 HTML / CSS
保险公司开门红口号
2014/06/21 职场文书
新员工考核评语
2014/12/31 职场文书
大学生个人学习总结
2015/02/15 职场文书
2016年中秋节慰问信
2015/12/01 职场文书
2019大学生社会实践报告汇总
2019/08/16 职场文书
JavaScript实现简单图片切换
2021/04/29 Javascript
详解Spring Boot使用系统参数表提升系统的灵活性
2021/06/30 Java/Android