同步加载的问题
默认的 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 的异步加载
- <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 的出现开始全面支持 deferdefer 属性规定当页面已完成加载后, 才会执行脚本 defer 属性仅适用于外部脚本(只有在使用 src 属性时)ps:ie 支持的 defer 似乎并非如此, 因为对 ie 无感, 不深究, 有兴趣的可以去查阅相关资料
既然 async 和 defer 经常一起出现, 那么辨析一下吧!
如果没有 async 和 defer 属性 (赋值为 true, 下同), 那么浏览器会立即执行当前的 js 脚本, 阻塞后面的脚本; 如果有 async 属性, 加载和渲染后续文档元素的过程将和当前 js 的加载与执行并行进行(异步); 如果有 defer 属性, 那么加载后续文档元素的过程将和 script.js 的加载并行进行(异步), 但是 script.js 的执行要在所有元素(DOM) 解析完成之后, DOMContentLoaded 事件触发之前完成
来看一张网上盗的图:
蓝色线代表网络读取, 红色线代表执行时间, 这俩都是针对脚本的; 绿色线代表 HTML 解析
此图告诉我们以下几个要点(摘自 defer 和 async 的区别):
defer 和 async 在网络读取 (下载) 这块儿是一样的, 都是异步的(相较于 HTML 解析)
它俩的差别在于脚本下载完之后何时执行, 显然 defer 是最接近我们对于应用脚本加载和执行的要求的
关于 defer, 此图未尽之处在于它是按照加载顺序执行脚本的, 这一点要善加利用
async 则是一个乱序执行的主, 反正对它来说脚本的加载和执行是紧紧挨着的, 所以不管你声明的顺序如何, 只要它加载完了就会立刻执行
仔细想想, 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 好用, 如果要考虑依赖的话, 可以选择 requirejsseajs 等模块加载器
总结
JavaScript 的异步加载还有一些方式, 比如: AJAX eval(使用 AJAX 得到脚本内容, 然后通过 eval(xmlhttp.responseText)来运行脚本)iframe 方式等
以上理解如果有出入, 还望指出~ 感谢大家对脚本之家的支持
来源: http://www.jb51.net/article/135116.htm