目录
一、JS为什么要延迟加载
对于js的优化(关于js的延迟加载)的好处是有助于提高页面加载速度,js延迟加载就是等页面加载完成之后在加载js文件.
HTML元素是按其在页面中出现的次序调用的,如果用javascript来管理页面上的元素(使用文档对象模型dom),并且js加载于欲操作的HTML元素之前,则代码将出错。也就是说,我们写了js语句来获取DOM对象,但由于DOM结构还没有加载完成,因此获取到的是空对象。
<head>
<script type="text/javascript">
var ul = document.getElementsByTagName('ul')[0]; //获取ul
var list = ul.getElementsByTagName('li');
for(var i =0;i<list.length;i++){
ul.appendChild(document.createElement('li'));
}
</script>
</head>
<body>
<ul>
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
</body>
</html>
二、区分阻塞加载、延迟加载、异步加载
1.阻塞加载
平常默认使用的都是阻塞加载。例如:
阻塞加载会阻止浏览器的后续处理,停止了后续的文件的解析,执行,如图像的渲染。为了这样可以让页面先显示出来,我们通常会把要加载的js放到body结束标签之前,使得js可在页面最后加载,尽量减少阻塞页面的渲染。
2.延迟加载:
延迟加载是脚本延迟到文档被完全解析和显示之后再执行。有些 js 代码并不是页面初始化的时候就立刻需要的,而稍后的某些情况才需要的,这个时候我们就可以通过延迟加载来执行这些不是立刻就需要的模块,就像图片的延迟加载,在图片出现在可视区域内时才加载显示图片。当页面有大量数据的时候使用延迟加载可以加快页面加载速率,给用户良好的体验。
3.异步加载:
异步加载是立即下载js'脚本的同时又不妨碍页面中的其他操作。
延迟加载和异步加载的相同点和不同点 | ||
延迟加载 | 异步加载 | |
相同点 | 并发执行 | |
只支持外部引入方式 | ||
不同点 | 多个js,按定义顺序执行 | 多个js,不一定按定义顺序执行 |
文档解析完才执行 | 加载完就找机会执行,在load事件之前 | |
在DOMContentLoaded事件之前执行完 | 在load事件之前执行完 |
三、js延迟加载的六种方式
一般有六种方式;defer属性、async属性、动态创建dom方式、使用jquery的getScript方法、使用setTimeout延迟方法、让js最后加载。
写的是六种方式,实际上自己在项目中真实用到的也就是让js最后加载。所以对这所谓的六种方式,可能仅作为一种知识储备,当以后的项目有这种问题需求了,可以有不同的解决思路。
1、defer属性(IE支持)
HTML 4.01为 <script>标签定义了defer属性(延迟脚本的执行)。
其用途是:表明脚本在执行时不会影响页面的构造,浏览器会立即下载,但延迟执行,即脚本会被延迟到整个页面都解析完毕之后再执行。
defer属性只适用于外部脚本文件,只有 Internet Explorer 支持 defer 属性。
并且defer属性解决了async引起的脚本顺序问题,使用defer属性,脚本将按照在页面中出现的顺序加载和运行。
示例1://脚本1
<script defer src="js/vendor/jquery.js"></script>
//脚本2
<script defer src="js/script2.js"></script>
//脚本3
<script defer src="js/script3.js"></script>
上述代码添加 defer
属性,脚本将按照在页面中出现的顺序加载,因此可确保脚本1必定加载于脚本2和 脚本3之前,同时脚本2必定加载于脚本3之前。(补充:最近在看《高级程序设计》里面说,实际上延迟脚本并不一定会按照顺序执行,也不一定会在DOMContenterLoaded事件触发前执行,因此最好只包含一个延迟脚本)
</head>
<script type="text/javascript" defer="defer" src="example1.js"></script>
<script type="text/javascript" defer="defer" src="example2.js"></script>
<body>
<!--这里是内容-->
</body>
在上面的例子中,虽然把<script>放在里<head>里,但是包含的脚本会延迟到整个页面之后,即延迟到浏览器遇到</html>标签后再执行。同时,在XHTML文档中,要把defer属性设置为defer="defer"
2、async属性
HTML 5为 <script>标签定义了async属性。添加此属性后,脚本和HTML将一并加载(异步),代码将顺利运行。
浏览器遇到async脚本时不会阻塞页面渲染,而是直接下载然后运行。但这样的问题是,不同脚本运行次序就无法控制,只是脚本不会阻止剩余页面的显示。
async属性只适用于外部脚本文件。
示例2:
//脚本1
<script async src="js/vendor/jquery.js"></script>
//脚本2
<script async src="js/script2.js"></script>
//脚本3
<script async src="js/script3.js"></script>
上述代码添加async 属性,这三者的调用顺序是不确定的,脚本1可以在脚本2和脚本3之前会之后调用,这是完全不确定的。如果脚本2和脚本3需要依赖脚本1中的函数,那么不确定的调用顺序会导致错误。(补充:异步脚本一定会在页面的load事件前执行,但可能会在DOMContenterLoaded事件触发之前或之后执行)
所以,当页面的不同脚本之间彼此独立,且不依赖于本页面的其他任何脚本时,async是最理想的选择。
总结:defer和async的异同点
相同:
- 加载文件时不会阻塞页面渲染
- 对于内部的js不起作用
- 使用这两个属性的脚本中不能调用document.write方法
区别:
- 如果脚本无需等待页面解析,且无依赖独立运行,那么应使用
async
。也就是每一个async属性的脚本都在它下载结束之后立即执行,同时会在window的load事件之前执行。 - 如果脚本需要等待解析,且依赖于其它脚本,调用这些脚本时应使用
defer
,将关联的脚本按所需顺序置于 HTML 中。
3、动态创建DOM方法
//这些代码应被放置在</body>标签前(接近HTML文件底部)
<script type="text/javascript">
function downloadJSAtOnload(){
var element = document.createElement("script");
element.src = "defer.js";
document.body.appendChild(element);
}
if (window.addEventListener) //添加监听事件
window.addEventListener("load",downloadJSAtOnload,false); //事件在冒泡阶段执行
else if (window.attachEvent)
window.attachEvent("onload",downloadJSAtOnload);
else
window.onload =downloadJSAtOnload;
</script>
4、使用jquery的getScript方法
getScript() 方法通过 HTTP GET 请求载入并执行 JavaScript 文件。
语法:jQuery.getScript(url,success(response,status))
url(必写):将要请求的 URL 字符串
success(response,status)(可选):规定请求成功后执行的回调函数。
其中的参数
response - 包含来自请求的结果数据
status - 包含请求的状态("success", "notmodified", "error", "timeout" 或 "parsererror")
//加载并执行 test.js:
$.getScript("test.js");
//加载并执行 test.js ,成功后显示信息
$.getScript("test.js", function(){
alert("Script loaded and executed.");
});
5、使用setTimeout延迟方法
<script type="text/javascript">
function A(){
$.post("/lord/login",{name:username,pwd:password},function(){
alert("Hello World!");
})
}
$(function (){
setTimeout("A()",1000); //延迟1秒
})
</script>
6、让js最后加载
将脚本元素放在文档体的底端(</body>标签前面),这样脚本就可以在HTML解析完毕后加载了。但此方案的问题是,只有在所有HTML DOM加载完成后才开始脚本的加载/解析过程。对于有大量js代码的大型网站,可能会带来显著的性能损耗。