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 相关文章推荐
斜45度寻路实现函数
Aug 20 Javascript
Javascript的各种节点操作实例演示代码
Jun 27 Javascript
JavaScript判断变量是对象还是数组的方法
Aug 28 Javascript
在浏览器中打开或关闭JavaScript的方法
Jun 03 Javascript
解析AngularJS中get请求URL出现的跨域问题
Dec 01 Javascript
JS中IP地址与整数相互转换的实现代码
Apr 10 Javascript
iOS + node.js使用Socket.IO框架进行实时通信示例
Apr 14 Javascript
jquery拖动改变div大小
Jul 04 jQuery
简单实现jQuery手风琴效果
Aug 18 jQuery
jQuery zTree 异步加载添加子节点重复问题
Nov 29 jQuery
JS实现验证码倒计时的注册页面
Jan 02 Javascript
JavaScript冒泡算法原理与实现方法深入理解
Jun 04 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为php增加openssl模块的方法
2011/06/14 PHP
ThinkPHP3.2.2的插件控制器功能
2015/03/05 PHP
php微信公众平台示例代码分析(二)
2016/12/06 PHP
PHP中Notice错误常见解决方法
2017/04/28 PHP
破解Session cookie的方法
2006/07/28 Javascript
javascript preload&amp;lazy load
2010/05/13 Javascript
往光标所在位置插入值的js代码
2013/09/22 Javascript
Javascript数组Array基础介绍
2016/03/13 Javascript
JS 动态加载js文件和css文件 同步/异步的两种简单方式
2016/09/23 Javascript
详解A标签中href=&quot;&quot;的几种用法
2017/08/20 Javascript
基于vue监听滚动事件实现锚点链接平滑滚动的方法
2018/01/17 Javascript
浅谈vue父子组件怎么传值
2018/07/21 Javascript
详解Element 指令clickoutside源码分析
2019/02/15 Javascript
JS中的算法与数据结构之集合(Set)实例详解
2019/08/20 Javascript
微信小程序将页面按钮悬浮固定在底部的实现代码
2020/10/29 Javascript
python静态方法实例
2015/01/14 Python
python 禁止函数修改列表的实现方法
2017/08/03 Python
Django中celery执行任务结果的保存方法
2019/07/12 Python
ORM Django 终端打印 SQL 语句实现解析
2019/08/09 Python
python获取array中指定元素的示例
2019/11/26 Python
jupyter notebook 重装教程
2020/04/16 Python
Bailey帽子官方商店:Bailey Hats
2018/09/25 全球购物
网上商城创业计划书范文
2014/01/31 职场文书
军训学生自我鉴定
2014/02/12 职场文书
就业协议书怎么填
2014/04/11 职场文书
小学生国庆节演讲稿
2014/09/05 职场文书
党员自我评议对照检查材料
2014/09/27 职场文书
环卫工作汇报材料
2014/10/28 职场文书
2014年路政工作总结
2014/12/10 职场文书
小班上学期幼儿评语
2014/12/30 职场文书
困难补助申请报告
2015/05/19 职场文书
人与自然观后感
2015/06/16 职场文书
小学入学感言
2015/08/01 职场文书
大学生社区义工服务心得体会
2016/01/22 职场文书
ORM模型框架操作mysql数据库的方法
2021/07/25 MySQL
如何利用React实现图片识别App
2022/02/18 Javascript