2016终版--script加载和执行
1.javascript的阻塞特性(问题的起源)
多数浏览器使用单一进程来处理用户界面(UI)刷新和javascript脚本执行,所以同一时刻只能做一件事。为什么使用单一进程呢?因为在脚本执行过程中可能会修改页面内容,所以只要看到script标签(无论内联还是外链)页面都会等待(页面的下载和渲染都停止)。
脚本的执行在默认的情况下都是同步的和阻塞的,当HTML解析器遇到script的时候,会默认先执行脚本,然后再恢复文档的解析和渲染。如果是一个由src引入的外部脚本,那么要先下载和执行外部脚本,然后才执行文档部分的内容。
2.javascript阻塞时长
首先js都是需要解析并执行的,另外对于外链的js,还需要花时间下载。这期间页面渲染(极端下白屏)和用户交互(极端下点不动)都是完全被阻塞的。
这再多说一句js的整个生命历程
a.通过http(s)请求下载文档(外链)
b.解析脚本
c.执行脚本(脚本中有事件逻辑的话跳到d)
d.事件驱动阶段,这个是异步的
3.javascript适合放置的位置
需要记住的一点:浏览器在解析到body标签之前(其实就是在解析head中的内容),不会渲染页面的任何部分,所以把js放到head是绝对要阻塞页面渲染的,不合适
最好的地方就是放在body的结束标签之前
4.javascript在一个页面的数量
现在的浏览器都允许并行下载js,但是要记住js的下载还是会阻止其他资源的下载,比如图片。(这个要验证一下)
过多外链的js也会发更多的http请求,所以个数越少越好。
5.无阻塞的脚本
对于前端而言,页面性能问题主要在于http请求数量和DOM更新频率(重排和重绘),这是两个值得优化的点。根据js优化原则如果我们只添加一个较大的js在页面,那么他也会锁死一段时间,这里面有个平衡的问题。所以我们希望在页面中添加一些js,这些js还不会阻塞浏览器,做这件事的技巧就在于页面加载完成后再加载js
5.1.html5新属性下的延迟加载脚本
延迟脚本执行--添加defer属性(defer="defer"),下载脚本的同时继续解析和渲染文档,在文档载入和解析完成后开始执行脚本,并且会按照文档中出现的顺序执行
5.2.html5新属性下的异步加载脚本
异步执行的脚本--添加async属性(async="async"),下载脚本的同时继续解析和渲染文档,在下载完成后就开始执行脚本,但是不保证顺序(在js执行的时候还是会阻塞UI的渲染的)
如果defer和async都有的话,忽略defer
5.3.js动态加载脚本(html5新属性前的土方法+onload时机)
//动态加载一个外部js文件
function loadScript( url ){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script);
}
loadScript("./sunny.js");
这种方法等同于(async="async")
5.4.ajax内联脚本(最大优点是:拉到脚本后不用立刻执行,自主选择时机)
$.ajax({
url: '/path/to/file',
type: 'default GET (Other values: POST)',
dataType: 'default: Intelligent Guess (Other values: xml, json, script, or html)',
data: {param1: 'value1'},
})
.done(function(code) {
console.log("success");
//动态加载一个内部js文件
function loadScriptString(){
var script = document.createElement("script");
script.type = "text/javascript";
try{
script.appendChild( document.createTextNode(code) );
}catch(ex){
script.text = code;
}
document.body.appendChild(script);
}
})
.fail(function() {
console.log("error");
});
...
//拉到脚本后不用立刻执行,自主选择时机
loadScriptString();
这种方法最大的有点就是下载解析后不执行,选择时间再执行
5.5.js加载顺序测试
//测试页面
//jquery-1.11.2.min.js中alert("000")
//jquery.validationEngine.js中alert("555")
//validation.js中alert("444")
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="js/jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="js/jquery.validationEngine.js" async="async"></script>
<script type="text/javascript" src="js/validation.js" defer="defer"></script>
</head>
<body>
<script type="text/javascript">
document.write(alert("666"));
</script>
<img id="test" src="http://bourbon.io/images/marketing/ruby.png">
<script type="text/javascript">
window.addEventListener("load",function(){
alert("111");
},false);
$(document).ready(function(){
alert("222");
});
$("#test").load(function(){
alert("333");
});
var script = document.createElement("script");
document.body.appendChild(script);
script.src = "js/jquery-1.11.2.min.js";
</script>
</body>
</html>
chrome: 000-666-444-222-555-333-000-111
IE11: 000-666-555-444-222-000-333-111
Firefox:000-666-555-444-333-000-222-111
说明如下:
1)000-666无争议,在head中的纯script在页面白屏的时候下载执行,000一定最先执行,之后document.write()随页面同步执行
2)555,因为是async,特点是下载非阻塞,下载后立即执行,所以我觉得可以是除了开头000和111外的任何位置,这个和下载组件的时长有关
3)444-222,综合三大浏览器来看,两者都是在doc.ready的时候执行,但是defer应该会比内联的ready要快,或者说因为这个test页面的defer位置在内联script的ready上面造成的
4)333-000,第二个000和333前后顺序不定,因为这两个下载时间不定
5)111,最后111无争议,在所有组件下载执行完成之后执行
参考过的文章包括:
1.高性能javascript--第一章
2.事件(3)关于js事件处理(持续更新)--关于load事件