document.write()
,例如清单 1<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的过程中,浏览器必须先花时间下载外链文件中的代码,然后解析并执行它。在这个过程中,页面渲染和用户交互完全被阻塞了。
<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 描述了页面加载过程中脚本和样式文件的下载过程。
我们可以发现一个有趣的现象:第一个 JavaScript 文件开始下载,与此同时阻塞了页面其他文件的下载。此外,从 script1.JS 下载完成到 script2.js 开始下载前存在一个延时,这段时间正好是 script1.js 文件的执行过程。每个文件必须等到前一个文件下载并执行完成才会开始下载。在这些文件逐个下载过程中,用户看到的是一片空白的页面。
从 IE 8、Firefox 3.5、Safari 4 和 Chrome 2 开始都允许并行下载 JavaScript 文件。这是个好消息,因为<script>标签在下载外部资源时不会阻塞其他<script>标签。遗憾的是,JavaScript 下载过程仍然会阻塞其他资源的下载,比如样式文件和图片。尽管脚本的下载过程不会互相影响,但页面仍然必须等待所有 JavaScript 代码下载并执行完成才能继续。因此,尽管最新的浏览器通过允许并行下载提高了性能,但问题尚未完全解决,脚本阻塞仍然是一个问题。
由于脚本会阻塞页面其他资源的下载,因此推荐将所有<script>标签尽可能放到<body>标签的底部,以尽量减少对整个页面下载的影响。例如清单 3
清单 3 推荐的代码放置位置示例
<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 type="text/javascript" src="script1.js" defer></script>带有 defer 属性的<script>标签可以放置在文档的任何位置。对应的 JavaScript 文件将在页面解析到<script>标签时开始下载,但不会执行,直到 DOM 加载完成,即onload事件触发前才会被执行。当一个带有 defer 属性的 JavaScript 文件下载时,它不会阻塞浏览器的其他进程,因此这类文件可以与其他资源文件一起并行下载。
<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 事件被触发前被调用。
var script = document.createElement ("script"); script.type = "text/javascript"; script.src = "script1.js"; document.getElementsByTagName("head")[0].appendChild(script);新的<script>元素加载 script1.js 源文件。此文件当元素添加到页面之后立刻开始下载。此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。您甚至可以将这些代码放在<head>部分而不会对其余部分的页面代码造成影响(除了用于下载文件的 HTTP 连接)。
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 有五种取值:
var script = document.createElement("script")script.type = "text/javascript";//Internet Explorerscript.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 实现所需的功能:
function loadScript(url, callback){ var script = document.createElement ("script") script.type = "text/javascript"; if (script.readyState){ //IEscript.onreadystatechange = function(){ if (script.readyState == "loaded" || script.readyState == "complete"){script.onreadystatechange = null;callback(); }}; } else { //Othersscript.onload = function(){ callback();}; } script.src = url; document.getElementsByTagName("head")[0].appendChild(script);}此函数接收两个参数:JavaScript 文件的 URL,和一个当 JavaScript 接收完成时触发的回调函数。属性检查用于决定监视哪种事件。最后一步,设置 src 属性,并将<script>元素添加至页面。此 loadScript() 函数使用方法如下:
loadScript("script1.js", function(){ alert("File is loaded!");});您可以在页面中动态加载很多 JavaScript 文件,但要注意,浏览器不保证文件加载的顺序。所有主流浏览器之中,只有 Firefox 和 Opera 保证脚本按照您指定的顺序执行。其他浏览器将按照服务器返回它们的次序下载并运行不同的代码文件。您可以将下载操作串联在一起以保证他们的次序,如下:
loadScript("script1.js", function(){ loadScript("script2.js", function(){loadScript("script3.js", function(){ alert("All files are loaded!");}); });});此代码等待 script1.js 可用之后才开始加载 script2.js,等 script2.js 可用之后才开始加载 script3.js。虽然此方法可行,但如果要下载和执行的文件很多,还是有些麻烦。如果多个文件的次序十分重要,更好的办法是将这些文件按照正确的次序连接成一个文件。独立文件可以一次性下载所有代码(由于这是异步进行的,使用一个大文件并没有什么损失)。
var xhr = new XMLHttpRequest();xhr.open("get", "script1.js", true);xhr.onreadystatechange = function(){ if (xhr.readyState == 4){if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ var script = document.createElement ("script"); script.type = "text/javascript"; script.text = xhr.responseText; document.body.appendChild(script);} }};xhr.send(null);此代码向服务器发送一个获取 script1.js 文件的 GET 请求。onreadystatechange 事件处理函数检查 readyState 是不是 4,然后检查 HTTP 状态码是不是有效(2XX 表示有效的回应,304 表示一个缓存响应)。如果收到了一个有效的响应,那么就创建一个新的<script>元素,将它的文本属性设置为从服务器接收到的 responseText 字符串。这样做实际上会创建一个带有内联代码的<script>元素。一旦新<script>元素被添加到文档,代码将被执行,并准备使用。
function loadJs(url, callback, charset) { var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); if ( !!charset) script.charset = "utf-8"; script.src = url; script.onload = script.onreadystatechange = function() {var f = script.readyState;if (f && f != "loaded" && f != "complete") return;script.onload = script.onreadystatechange = null;head.removeChild(script) if (callback) { callback() || callback}; }; head.appendChild(script);}
// js同步加载function getScripts(i, linkArray, fn) { env || getEnv(); var script = document.createElement("script"); script.type = "text/javascript"; script.src = linkArray[i]; var head = document.head || document.getElementsByTagName("head")[0]; head.appendChild(script); if (env.ie && "onreadystatechange" in script && !("draggable" in script)){ //ie浏览器使用以下方式加载script.onreadystatechange = function () { if (/loaded|complete/.test(script.readyState)) { script.onreadystatechange = null; if(i === linkArray.length-1) {if (fn) { fn();} } else {getScripts(++i, linkArray, fn); } }}; }else{script.onload = function() { if(i === linkArray.length-1) {if (fn) { fn();} } else {getScripts(++i, linkArray, fn); }}; }}// js存在依赖关系 依次加载getScripts(0, [ "http://caibaojian.com/demo/base.js", "http://caibaojian.com/demo/reset.js"], function() {alert("callback");});总结