|
<html>
<head>
<title>Source Example</title>
</head>
<body>
<p>
<script type="text/javascript">
document.write("Today is " + (new Date()).toDateString());
</script>
</p>
</body>
</html>
当浏览器遇到<script>标签时,当前HTML页面无从获知JavaScript是否会向<p> 标签添加内容,或引入其余元素,或甚至移除该标签。所以,这时浏览器会中止处理页面,先执行JavaScript代码,而后再继续解析和渲染页面。一样的状况也发生在使用src属性加载JavaScript的过程当中,浏览器必须先花时间下载外链文件中的代码,而后解析并执行它。在这个过程当中,页面渲染和用户交互彻底被阻塞了。 javascript
脚本位置
HTML 4 规范指出 <script> 标签能够放在 HTML 文档的<head>或<body>中,并容许出现屡次。Web 开发人员通常习惯在 <head> 中加载外链的 JavaScript,接着用 <link> 标签用来加载外链的 CSS 文件或者其余页面信息。例如清单 2
<html>
<head>
<title>Source Example</title>
<script type="text/javascript" src="script1.js"></script>
<script type="text/javascript" src="script2.js"></script>
<script type="text/javascript" src="script3.js"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<p>Hello world!</p>
</body>
</html>
然而这种常规的作法却隐藏着严重的性能问题。在清单 2 的示例中,当浏览器解析到 <script> 标签(第 4 行)时,浏览器会中止解析其后的内容,而优先下载脚本文件,并执行其中的代码,这意味着,其后的 styles.css 样式文件和<body>标签都没法被加载,因为<body>标签没法被加载,那么页面天然就没法渲染了。所以在该JavaScript代码彻底执行完以前,页面都是一片空白。图 1 描述了页面加载过程当中脚本和样式文件的下载过程。 css
JavaScript 文件的加载和执行阻塞其余文件的下载 html
咱们能够发现一个有趣的现象:第一个 JavaScript 文件开始下载,与此同时阻塞了页面其余文件的下载。此外,从 script1.js 下载完成到 script2.js 开始下载前存在一个延时,这段时间正好是 script1.js 文件的执行过程。每一个文件必须等到前一个文件下载并执行完成才会开始下载。在这些文件逐个下载过程当中,用户看到的是一片空白的页面。
从 IE 八、Firefox 3.五、Safari 4 和 Chrome 2 开始都容许并行下载 JavaScript 文件。这是个好消息,由于<script>标签在下载外部资源时不会阻塞其余<script>标签。遗憾的是,JavaScript 下载过程仍然会阻塞其余资源的下载,好比样式文件和图片。尽管脚本的下载过程不会互相影响,但页面仍然必须等待全部 JavaScript 代码下载并执行完成才能继续。所以,尽管最新的浏览器经过容许并行下载提升了性能,但问题还没有彻底解决,脚本阻塞仍然是一个问题。
因为脚本会阻塞页面其余资源的下载,所以推荐将全部<script>标签尽量放到<body>标签的底部,以尽可能减小对整个页面下载的影响。例如 java
<html>
<head>
<title>Source Example</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<p>Hello world!</p>
<!-- Example of efficient script positioning -->
<script type="text/javascript" src="script1.js"></script>
<script type="text/javascript" src="script2.js"></script>
<script type="text/javascript" src="script3.js"></script>
</body>
</html>
这段代码展现了在 HTML 文档中放置<script>标签的推荐位置。尽管脚本下载会阻塞另外一个脚本,可是页面的大部份内容都已经下载完成并显示给了用户,所以页面下载不会显得太慢。这是优化 JavaScript 的首要规则:将脚本放在底部。
组织脚本
因为每一个<script>标签初始下载时都会阻塞页面渲染,因此减小页面包含的<script>标签数量有助于改善这一状况。这不只针对外链脚本,内嵌脚本的数量一样也要限制。浏览器在解析 HTML 页面的过程当中每遇到一个<script>标签,都会因执行脚本而致使必定的延时,所以最小化延迟时间将会明显改善页面的整体性能。这个问题在处理外链 JavaScript 文件时略有不一样。考虑到 HTTP 请求会带来额外的性能开销,所以下载单个 100Kb 的文件将比下载 5 个 20Kb 的文件更快。也就是说,减小页面中外链脚本的数量将会改善性能。一般一个大型网站或应用须要依赖数个 JavaScript 文件。您能够把多个文件合并成一个,这样只须要引用一个<script>标签,就能够减小性能消耗。文件合并的工做可经过离线的打包工具或者一些实时的在线服务来实现。
须要特别提醒的是,把一段内嵌脚本放在引用外链样式表的<link>以后会致使页面阻塞去等待样式表的下载。这样作是为了确保内嵌脚本在执行时能得到最精确的样式信息。所以,建议不要把内嵌脚本紧跟在<link>标签后面。
无阻塞的脚本
减小 JavaScript 文件大小并限制 HTTP 请求数在功能丰富的 Web 应用或大型网站上并不老是可行。Web 应用的功能越丰富,所须要的 JavaScript 代码就越多,尽管下载单个较大的 JavaScript 文件只产生一次 HTTP 请求,却会锁死浏览器的一大段时间。为避免这种状况,须要经过一些特定的技术向页面中逐步加载 JavaScript 文件,这样作在某种程度上来讲不会阻塞浏览器。无阻塞脚本的秘诀在于,在页面加载完成后才加载 JavaScript 代码。这就意味着在 window 对象的onload事件触发后再下载脚本。有多种方式能够实现这一效果。
延迟加载脚本
HTML 4 为<script>标签订义了一个扩展属性:defer。Defer 属性指明本元素所含的脚本不会修改 DOM,所以代码能安全地延迟执行。defer 属性只被 IE 4 和 Firefox 3.5 更高版本的浏览器所支持,因此它不是一个理想的跨浏览器解决方案。在其余浏览器中,defer 属性会被直接忽略,所以<script>标签会以默认的方式处理,也就是说会形成阻塞。然而,若是您的目标浏览器支持的话,这仍然是个有用的解决方案。 浏览器
<script type="text/javascript" src="script1.js" defer></script> 缓存
带有defer属性的<script>标签能够放置在文档的任何位置。对应的 JavaScript 文件将在页面解析到<script>标签时开始下载,但不会执行,直到 DOM 加载完成,即onload事件触发前才会被执行。当一个带有 defer 属性的 JavaScript 文件下载时,它不会阻塞浏览器的其余进程,所以这类文件能够与其余资源文件一块儿并行下载。
任何带有 defer 属性的<script>元素在 DOM 完成加载以前都不会被执行,不管内嵌或者是外链脚本都是如此。清单 5 的例子展现了defer属性如何影响脚本行为:
<html>
<head>
<title>Script Defer Example</title>
</head>
<body>
<script type="text/javascript" defer>
alert("defer");
</script>
<script type="text/javascript">
alert("script");
</script>
<script type="text/javascript">
window.onload = function(){
alert("load");
};
</script>
</body>
</html>
这段代码在页面处理过程当中弹出三次对话框。不支持 defer 属性的浏览器的弹出顺序是:“defer”、“script”、“load”。而在支持 defer 属性的浏览器上,弹出的顺序则是:“script”、“defer”、“load”。请注意,带有 defer 属性的<script>元素不是跟在第二个后面执行,而是在 onload 事件被触发前被调用。
若是您的目标浏览器只包括 Internet Explorer 和 Firefox 3.5,那么 defer 脚本确实有用。若是您须要支持跨领域的多种浏览器,那么还有更一致的实现方式。
HTML 5 为<script>标签订义了一个新的扩展属性:async。它的做用和 defer 同样,可以异步地加载和执行脚本,不由于加载脚本而阻塞页面的加载。可是有一点须要注意,在有 async 的状况下,JavaScript 脚本一旦下载好了就会执行,因此颇有可能不是按照本来的顺序来执行的。若是 JavaScript 脚本先后有依赖性,使用 async 就颇有可能出现错误。
动态脚本元素
文档对象模型(DOM)容许您使用 JavaScript 动态建立 HTML 的几乎所有文档内容。<script>元素与页面其余元素同样,能够很是容易地经过标准 DOM 函数建立: 安全
var script = document.createElement ("script");
script.type = "text/javascript";
script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);
新的<script>元素加载 script1.js 源文件。此文件当元素添加到页面以后马上开始下载。此技术的重点在于:不管在何处启动下载,文件的下载和运行都不会阻塞其余页面处理过程。您甚至能够将这些代码放在<head>部分而不会对其他部分的页面代码形成影响(除了用于下载文件的 HTTP 链接)。
当文件使用动态脚本节点下载时,返回的代码一般当即执行(除了 Firefox 和 Opera,他们将等待此前的全部动态脚本节点执行完毕)。当脚本是“自运行”类型时,这一机制运行正常,可是若是脚本只包含供页面其余脚本调用调用的接口,则会带来问题。这种状况下,您须要跟踪脚本下载完成并是否准备妥善。可使用动态 <script> 节点发出事件获得相关信息。
Firefox、Opera, Chorme 和 Safari 3+会在<script>节点接收完成以后发出一个 onload 事件。您能够监听这一事件,以获得脚本准备好的通知: 服务器
var script = document.createElement ("script")
script.type = "text/javascript";
//Firefox, Opera, Chrome, Safari 3+
script.onload = function(){
alert("Script loaded!");
};
script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);
Internet Explorer 支持另外一种实现方式,它发出一个 readystatechange 事件。<script>元素有一个readyState 属性,它的值随着下载外部文件的过程而改变。readyState 有五种取值: 网络
经过检查readyState状态加载JavaScript脚本:
app
var script = document.createElement("script")
script.type = "text/javascript";
//Internet Explorer
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange = null;
alert("Script loaded.");
}
};
script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);
大多数状况下,您但愿调用一个函数就能够实现JavaScript文件的动态加载。下面的函数封装了标准实现和 IE 实现所需的功能:
经过函数进行封装
JS代码
经过函数进行封装
JS代码
loadScript()函数使用方法
JS代码
经过 loadScript()函数加载多个JavaScript脚本
JS代码
经过 XHR 对象加载 JavaScript 脚本
JS代码