高性能JavaScript - 加载和执行
### 无阻塞的脚本
减少`JavaScript`文件大小,并限制`HTTP`请求数,仅仅是创建响应迅速的Web应用的第一步。Web应用功能越丰富,所需的`JavaScript`代码就越多,所以精简源代码并不总是可行。尽管下载单个较大的`JavaScript`文件只会产生一次HTTP请求,却会锁死浏览器一大段时间。为避免这种情况,我们需要向页面中逐步加载`JavaScript`代码,这样做在某种程度上来说不会阻塞浏览器。
无阻塞脚本的秘诀在于,在页面加载完成后才加载`JavaScript`代码。即在`window`对象的`load`事件触发后再下载脚本。
### 延迟的脚本
```
<script type="text/javascript" src="./init.js" defer="defer"></script>
<script src="./init.js" async="async" />
```
- [defer](http://www.w3school.com.cn/tags/att_script_defer.asp) 只有 Internet Explorer 支持 defer 属性。
- [async](http://www.w3school.com.cn/tags/att_script_async.asp) async 属性是 HTML5 中的新属性。
### 动态脚本元素
```
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "./init.js";
document.getElementsByTagName("head")[0].appendChild(script);
```
### XMLHttpRequest 脚本注入
```
var xhr = new XMLHttpRequest();
xhr.open("get", "./init.js", true);
xhr.onreadystatechange = function () {
/**
* Uninitialized (0) - 初始化状态
* Open (1) - open() 方法已调用,但是 send() 方法未调用;请求还没有被发送
* Sent (2) - Send() 方法已调用,HTTP 请求已发送到 Web 服务器。未接收到响应。
* Receiving (3) - 所有响应头部都已经接收到。响应体开始接收但未完成。
* Loaded (4) - HTTP 响应已经完全接收。
*/
if (xhr.readyState == 4) {
var status = xhr.status; // HTTP状态码
if (status >= 200 && status < 300 || status == 304) {
var script = document.createElement('script');
script.type = "text/javascript";
script.text = xhr.responseText;
document.body.appendChild(script);
}
}
}
xhr.send(null);
```
### 推荐的无阻塞模式
```
/**
* 加载脚本
* url [String] 要加载的脚本地址
* callback [Function] 加载成功后的回调函数
*/
function loadScript (url, callback) {
var script = document.createElement('script');
script.type = "text/javascript";
if (script.readyState) { // IE浏览器方案
script.onreadystatechange = function () {
/**
* uninitialized: 初始状态
* loading: 开始下载
* loaded: 下载完成
* interactive: 数据下载完成但尚不可用
* complete: 所有数据已准备就绪
*/
var readyState = script.readyState;
if (readyState == "loaded" || readyState == "complete") {
script.onreadystatechange = null;
callback();
}
}
} else {
script.onload = function () {
callback();
}
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
loadScript("./init.js", function () {
Application.init();
})
```
### 小结
每次遇到`<script>`标签都会阻塞浏览器的其他进程,如界面渲染;页面需要等待代码下载并执行完成,才可以渲染。推荐一下几种优化方案:
1、将所有的`<script>`标签放在`</body>`闭合标签前;确保在脚本执行前页面已经渲染完成。
2、合并脚本。减少`<script>`标签使 加载 和 响应都变得更快。
3、无阻塞下载方法:
- 使用`<script>`标签的`defer`属性
- 使用动态创建`<script>`元素来下载并执行代码。
- 使用`XHR`对象下载代码并注入页面执行