前言
转自原文 JavaScript
的性能优化:加载和执行
随着 Web2.0
技术的不断推广,越来越多的应用使用威尼斯手机娱乐官网,javascript技术在客户端进行处理,从而使
JavaScript
在浏览器中的性能成为开发者所面临的最重要的可用性问题。而这个问题又因
JavaScript 的阻塞特性变的复杂,也就是说当浏览器在执行 JavaScript
代码时,不能同时做其他任何事情。
在J2EE编程中,我们接触最多的脚本语言还是JavaScript。在使用JavaScript中我们通常会考虑其性能问题,因此将日常总结的关于JavaScript性能优化的方式总结出来,以备查询。
JavaScript无阻塞加载性能优化方案(1)
Javascript在浏览器中的性能,可以说是前端开发者所要面对的最重要的可用性问题。
在Yahoo的Yslow23条规则当中,其中一条是将JS放在底部 。原因是,事实上,大多数浏览器使用单进程处理UI和更新Javascript运行等多个任务,而同一时间只能有一个任务被执行。Javascript运行了多长时间,那么在浏览器空闲下来响应用户交互之前的等待时间就有多长。
从基本层面说,这意味着<script>标签的出现使整个页面因脚本解析、运行而出现等待。不论实际的
JavaScript
代码是内联的还是包含在一个不相干的外部文件中,页面下载和解析过程必须停下,等待脚本
完成这些处理,然后才能继续。这是页面生命周期必不可少的部分,因为脚本可能在运行过程中修改页面
内容。典型的例子是 document.write()函数,例如:
<html> <head> <title>Script Example</title> </head> <body> <p> <script type="text/javascript"> document.write("The date is " + (new Date()).toDateString()); </script> </p> </body> </html>
当浏览器遇到一个<script>标签时,正如上面 HTML 页面中那样,无法预知
JavaScript 是否在<p>标签中 添加内容。因此,浏览器停下来,运行此
JavaScript 代码,然后再继续解析、翻译页面。同样的事情发生 在使用 src
属性加载 JavaScript
的过程中。浏览器必须首先下载外部文件的代码,这要占用一些时间,然后
解析并运行此代码。此过程中,页面解析和用户交互是被完全阻塞的。
因为脚本阻塞其他页面资源的下载过程,所以推荐的办法是:将所有<script>标签放在尽可能接近<body>
标签底部的位置,尽量减少对整个页面下载的影响。例如:
<html> <head> <title>Script Example</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <p>Hello world!</p> <-- Example of recommended script positioning --> <script type="text/javascript" src="file1.js"></script> <script type="text/javascript" src="file2.js"></script> <script type="text/javascript" src="file3.js"></script> </body> </html>
此代码展示了所推荐的<script>标签在 HTML
文件中的位置。尽管脚本下载之间互相阻塞,但页面已经
下载完成并且显示在用户面前了,进入页面的速度不会显得太慢。这就是上面提到的将JS放到底部。
另外,Yahoo! 为他的“Yahoo! 用户接口(Yahoo! User
Interface,YUI)”库创建一个“联合句柄”,这是通过他 们的“内容投递网络(Content
Delivery Network,CDN)”实现的。任何一个网站可以使用一个“联合句柄”URL
指出包含 YUI 文件包中的哪些文件。例如,下面的 URL 包含两个文件:
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js"></script>
此 URL 调用 2.7.0 版本的 yahoo-min.js 和 event-min.js
文件。这些文件在服务器上是两个分离的文件,但是 当服务器收到此 URL
请求时,两个文件将被合并在一起返回给客户端。通过这种方法,就不再需要两个
<script>标签(每个标签加载一个文件),一个<script>标签就可以加载他们。这是在HTML页面包含多个外部Javascript的最佳方法。
Noblocking Scripts 非阻塞脚本
上述是页面初始状态包含多个Javascript脚本加载的最佳方法。Javascript倾向于阻塞浏览器某些处理过程,如http请求和界面刷新,这是开发者面临的最显著性能问题。保持Javascript文件短小,并限制http请求的数量,只是创建反应迅速的网页应用第一步。
但诸如大型网页有大量的Js代码,保持源码短小并不总是一种最佳选择。So,非阻塞脚本应运而生,我们需要的是向页面中逐步添加javascript,某种程度上而言不会阻塞浏览器。
而非阻塞脚本的关键在于,等页面完成加载之后,再加载Javascript源码,这意味着在window的load事件发出之后开始下载代码。
相关解释:
window 的load事件只会在页面载入完毕后触发一次且仅一次。
window.onload=function(){}必须等待网页中所有的内容加载完毕后 (
包括元素的所有关联文件,例如图片 ) 才能执行,即Javascript此时才可以访问页面中的任何元素。
如下述几种方法:
Deferred Scripts 延期脚本
Html4为<script>标签定义了一个扩展属性:defer。
这个defer属性指明元素中所包含的脚本不打算修改DOM,因此代码可以稍后执行。defer属性只被Internet
Explorer 4+和Firefox
3.5+支持,它不是一个理想的跨浏览器解决方案。在其他浏览器上,defer属性将被忽略。所以,<script>标签会按照正常默认方式处理,即是会造成阻塞。如果得到各个主流浏览器的支持,这仍是一种有效的解决方式。
<script type="text/javascript" src="file1.js" defer></script>
一个带有defer属性的<script>标签可以放置在文档的任何位置,它会在被解析时启动下载,直到DOM加载完成在onload事件句柄被调用之前)。当一个defer的Javascript文件被下载时,它不会阻塞浏览器的其他处理过程,所以这些文件可以与其他资源一起并行下载。
可以使用下述代码测试浏览器是否支持defer属性:
<html> <head> <title>Script Defer Example</title> </head> <body> <script defer> alert("defer");</script> <script> alert("script"); </script> <script> window.onload = function(){ alert("load");}; </script> </body> </html>
如果浏览器不支持defer,那么弹出的对话框的顺序是“defer”,“script”,“load”。
如果浏览器支持defer,那么弹出的对话框的顺序是“script”,“load”,“defer”。
)
Javascript在浏览器中的性能,可以说是前端开发者所要面对的最重要的可用性问题。
在Yahoo的Yslow23条规则…
无论当前 JavaScript
代码是内嵌还是在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成。JavaScript
执行过程耗时越久,浏览器等待响应用户输入的时间就越长。浏览器在下载和执行脚本时出现阻塞的原因在于,脚本可能会改变页面或
JavaScript
的命名空间,它们对后面页面内容造成影响。一个典型的例子就是在页面中使用document.write()
,例如清单
1
使用动态创建的元素来下载并执行代码;
在使用JavaScript中经常会遇到字符串的拼接问题。请问大家在使用Java编程的时候遇到上述的问题,该怎么处理?
清单 1 JavaScript 代码内嵌示例
无论当前 JavaScript
代码是内嵌还是在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成。JavaScript
执行过程耗时越久,浏览器等待响应用户输入的时间就越长。浏览器在下载和执行脚本时出现阻塞的原因在于,脚本可能会改变页面或
JavaScript
的命名空间,它们对后面页面内容造成影响。一个典型的例子就是在页面中使用document.write()
。例如清单
1
使用 XHR 对象下载 JavaScript 代码并注入页面中。
NX学生:老师,使用StringBulider 或StringBuffer
<html>
<head>
<title>Source Example</title>
</head>
<body>
<p>
<script type="text/javascript">
document.write("Today is " + (new Date()).toDateString());
</script>
</p>
</body>
</html>
清单 1 JavaScript 代码内嵌示例
1
2
3
4
5
6
7
8
9
10
11
12
|
< 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 的 Web
网站和应用的实际性能。
SB老师:回答正确,使用StringBuilder或StringBuffer能够避免过多创建对象,降低系统性能。
当浏览器遇到<script>标签时,当前 html 页面无从获知 JavaScript
是否会向<p>
标签添加内容,或引入其他元素,或甚至移除该标签。因此,这时浏览器会停止处理页面,先执行
JavaScript代码,然后再继续解析和渲染页面。同样的情况也发生在使用 src
属性加载
JavaScript的过程中,浏览器必须先花时间下载外链文件中的代码,然后解析并执行它。在这个过程中,页面渲染和用户交互完全被阻塞了。
脚本位置
HTML 4 规范指出 <script>
标签可以放在 HTML
文档的<head>
或<body>
中,并允许出现多次。Web 开发人员一般习惯在
<head>
中加载外链的 JavaScript,接着用 <link>
标签用来加载外链的 CSS
文件或者其他页面信息。例如清单 2
原文来自:
好了,回到正题,我们还是来解答在使用JavaScript中,遇到上述问题的处理方式。
脚本位置
清单 2 低效率脚本位置示例
1
2
3
4
5
6
7
8
9
10
11
12
|
< 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
描述了页面加载过程中脚本和样式文件的下载过程。
补充几个平时自己在用的函数
首先,先来看看NX学生的处理方式:
HTML 4 规范指出 <script> 标签可以放在 HTML
文档的<head>或<body>中,并允许出现多次。web
开发人员一般习惯在 <head> 中加载外链的 JavaScript,接着用
<link> 标签用来加载外链的 CSS 文件或者其他页面信息。例如清单 2
图 1 JavaScript 文件的加载和执行阻塞其他文件的下载
我们可以发现一个有趣的现象:第一个 JavaScript
文件开始下载,与此同时阻塞了页面其他文件的下载。此外,从 script1.js
下载完成到 script2.js 开始下载前存在一个延时,这段时间正好是 script1.js
文件的执行过程。每个文件必须等到前一个文件下载并执行完成才会开始下载。在这些文件逐个下载过程中,用户看到的是一片空白的页面。
从 IE 8、Firefox 3.5、Safari 4 和 Chrome 2 开始都允许并行下载 JavaScript
文件。这是个好消息,因为<script>
威尼斯人彩票,标签在下载外部资源时不会阻塞其他<script>
标签。遗憾的是,JavaScript
下载过程仍然会阻塞其他资源的下载,比如样式文件和图片。尽管脚本的下载过程不会互相影响,但页面仍然必须等待所有
JavaScript
代码下载并执行完成才能继续。因此,尽管最新的浏览器通过允许并行下载提高了性能,但问题尚未完全解决,脚本阻塞仍然是一个问题。
由于脚本会阻塞页面其他资源的下载,因此推荐将所有<script>
标签尽可能放到<body>
标签的底部,以尽量减少对整个页面下载的影响。例如清单
3
function loadJs(url, callback, charset) {
[html]
<html>
<script type=”text/javascript”>
var string=”begin”
var date = new Date()
var begin = date.getTime()
for(var i=0;i<9999999;i++){
string+=”abc”
}
alert(new Date().getTime() – begin)
</script>
</html>
清单 2 低效率脚本位置示例
清单 3 推荐的代码放置位置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
< 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 的首要规则:将脚本放在底部。
var head = document.getElementsByTagName(“head”)[0];
<html>
<script type=”text/javascript”>
var string=”begin”
var date = new Date()
var begin = date.getTime()
<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>
组织脚本
由于每个<script>
标签初始下载时都会阻塞页面渲染,所以减少页面包含的<script>
标签数量有助于改善这一情况。这不仅针对外链脚本,内嵌脚本的数量同样也要限制。浏览器在解析
HTML
页面的过程中每遇到一个<script>
标签,都会因执行脚本而导致一定的延时,因此最小化延迟时间将会明显改善页面的总体性能。
这个问题在处理外链 JavaScript 文件时略有不同。考虑到 HTTP
请求会带来额外的性能开销,因此下载单个 100Kb 的文件将比下载 5 个 20Kb
的文件更快。也就是说,减少页面中外链脚本的数量将会改善性能。
通常一个大型网站或应用需要依赖数个 JavaScript
文件。您可以把多个文件合并成一个,这样只需要引用一个<script>
标签,就可以减少性能消耗。文件合并的工作可通过离线的打包工具或者一些实时的在线服务来实现。
需要特别提醒的是,把一段内嵌脚本放在引用外链样式表的<link>
之后会导致页面阻塞去等待样式表的下载。这样做是为了确保内嵌脚本在执行时能获得最精确的样式信息。因此,建议不要把内嵌脚本紧跟在<link>
标签后面。
var script = document.createElement(“script”); if ( !!charset)
script.charset = “utf-8”;
for(var i=0;i<9999999;i++){
string+=”abc”
}
alert(new Date().getTime() – begin)
</script>
</html>SB老师一看NX学生实现的方式,差点笑喷。这种垃圾的实现方式,真是毁了你一世英名啊。
然而这种常规的做法却隐藏着严重的性能问题。在清单 2
的示例中,当浏览器解析到 <script> 标签(第 4
行)时,浏览器会停止解析其后的内容,而优先下载脚本文件,并执行其中的代码,这意味着,其后的
styles.css
样式文件和<body>标签都无法被加载,由于<body>标签无法被加载,那么页面自然就无法渲染了。因此在该
JavaScript 代码完全执行完之前,页面都是一片空白。图 1
描述了页面加载过程中脚本和样式文件的下载过程。
无阻塞的脚本
减少 JavaScript 文件大小并限制 HTTP 请求数在功能丰富的 Web
应用或大型网站上并不总是可行。Web 应用的功能越丰富,所需要的 JavaScript
代码就越多,尽管下载单个较大的 JavaScript 文件只产生一次 HTTP
请求,却会锁死浏览器的一大段时间。为避免这种情况,需要通过一些特定的技术向页面中逐步加载
JavaScript 文件,这样做在某种程度上来说不会阻塞浏览器。
无阻塞脚本的秘诀在于,在页面加载完成后才加载 JavaScript
代码。这就意味着在 window
对象的
onload
事件触发后再下载脚本。有多种方式可以实现这一效果。
script.src = url;
SB老师面带微笑的说道:你这种实现方式就相当于小学生的水平,就是一堆垃圾字母的随机组合。说完只听见SB老师在键盘上键步如飞的敲其代码来。NX学生还没有反应过来,SB老师已经将代码完成:
图 1 JavaScript 文件的加载和执行阻塞其他文件的下载
延迟加载脚本
HTML 4 为<script>
标签定义了一个扩展属性:defer
。Defer
属性指明本元素所含的脚本不会修改 DOM,因此代码能安全地延迟执行。defer
属性只被 IE 4 和 Firefox 3.5
更高版本的浏览器所支持,所以它不是一个理想的跨浏览器解决方案。在其他浏览器中,defer
属性会被直接忽略,因此<script>
标签会以默认的方式处理,也就是说会造成阻塞。然而,如果您的目标浏览器支持的话,这仍然是个有用的解决方案。清单
4 是一个例子
script.onload = script.onreadystatechange = function() {
[html]
<html>
<script type=”text/javascript”>
var string=”begin”;
var string01=[“begin”];
var date = new Date();
var begin = date.getTime();
for(var i=0;i<55555555;i++){
//string+=”abc”;
string01.push(“abc”);
}
var result = string01.join(“”);
alert(new Date().getTime() – begin);
</script>
</html>
清单 4 defer 属性使用方法示例
1
|
<script type="text/javascript" src="script1.js" defer> </script>
|
带有 defer
属性的<script>
标签可以放置在文档的任何位置。对应的
JavaScript 文件将在页面解析到<script>
标签时开始下载,但不会执行,直到
DOM 加载完成,即onload
事件触发前才会被执行。当一个带有 defer
属性的
JavaScript
文件下载时,它不会阻塞浏览器的其他进程,因此这类文件可以与其他资源文件一起并行下载。
任何带有 defer
属性的<script>
元素在 DOM
完成加载之前都不会被执行,无论内嵌或者是外链脚本都是如此。清单 5
的例子展示了defer
属性如何影响脚本行为:
var f = script.readyState; if (f && f != “loaded” && f !=
“complete”) return;
<html>
<script type=”text/javascript”>
var string=”begin”;
var string01=[“begin”];
var date = new Date();
var begin = date.getTime();
for(var i=0;i<55555555;i++){
//string+=”abc”;
string01.push(“abc”);
}
var result = string01.join(“”);
alert(new Date().getTime() – begin);
</script>
</html>通过对以上代码的运行比较,SB老师的代码性能明显优于NX学生的代码,NX学生代码还会经常导致IE宕掉。
我们可以发现一个有趣的现象:第一个 JavaScript
文件开始下载,与此同时阻塞了页面其他文件的下载。此外,从 script1.JS
下载完成到 script2.js 开始下载前存在一个延时,这段时间正好是 script1.js
文件的执行过程。每个文件必须等到前一个文件下载并执行完成才会开始下载。在这些文件逐个下载过程中,用户看到的是一片空白的页面。
清单 5 defer 属性对脚本行为的影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
< 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
就很有可能出现错误。
script.onload = script.onreadystatechange = null;
NX学生看到代码运行的结果,顿时对SB老师产生仰慕之情,决定虚心想SB老师学习,不断提升自己……
这个故事虽然告一段落,但是SB老师和NX学生的JavaScript之旅还在继续……
从 IE 8、Firefox 3.5、Safari 4 和 Chrome 2 开始都允许并行下载 JavaScript
文件。这是个好消息,因为<script>标签在下载外部资源时不会阻塞其他<script>标签。遗憾的是,JavaScript
下载过程仍然会阻塞其他资源的下载,比如样式文件和图片。尽管脚本的下载过程不会互相影响,但页面仍然必须等待所有
JavaScript
代码下载并执行完成才能继续。因此,尽管最新的浏览器通过允许并行下载提高了性能,但问题尚未完全解决,脚本阻塞仍然是一个问题。
动态脚本元素
文档对象模型(DOM)允许您使用 JavaScript 动态创建 HTML
的几乎全部文档内容。<script>
元素与页面其他元素一样,可以非常容易地通过标准
DOM 函数创建:
head.removeChild(script) if (callback) {
由于脚本会阻塞页面其他资源的下载,因此推荐将所有<script>标签尽可能放到<body>标签的底部,以尽量减少对整个页面下载的影响。例如清单
3
清单 6 通过标准 DOM 函数创建<script>元素
1
2
3
4
|
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
事件。您可以监听这一事件,以得到脚本准备好的通知:
callback() || callback
清单 3 推荐的代码放置位置示例
清单 7 通过监听 onload 事件加载 JavaScript 脚本
1
2
3
4
5
6
7
8
9
10
|
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
有五种取值:
- “uninitialized”:默认状态
- “loading”:下载开始
- “loaded”:下载完成
- “interactive”:下载完成但尚不可用
- “complete”:所有数据已经准备好
微软文档上说,在<script>
元素的生命周期中,readyState
的这些取值不一定全部出现,但并没有指出哪些取值总会被用到。实践中,我们最感兴趣的是“loaded”和“complete”状态。Internet
Explorer 对这两个 readyState
值所表示的最终状态并不一致,有时<script>
元素会得到“loader”却从不出现“complete”,但另外一些情况下出现“complete”而用不到“loaded”。最安全的办法就是在
readystatechange
事件中检查这两种状态,并且当其中一种状态出现时,删除
readystatechange
事件句柄(保证事件不会被处理两次):
};
…
<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>
清单 8 通过检查 readyState 状态加载 JavaScript 脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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 实现所需的功能:
};
这段代码展示了在 HTML
文档中放置<script>标签的推荐位置。尽管脚本下载会阻塞另一个脚本,但是页面的大部分内容都已经下载完成并显示给了用户,因此页面下载不会显得太慢。这是优化
JavaScript 的首要规则:将脚本放在底部。
清单 9 通过函数进行封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
function loadScript(url, callback){
var script = document.createElement ("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
|
此函数接收两个参数:JavaScript 文件的 URL,和一个当 JavaScript
接收完成时触发的回调函数。属性检查用于决定监视哪种事件。最后一步,设置
src
属性,并将<script>
元素添加至页面。此 loadScript()
函数使用方法如下:
head.appendChild(script);
组织脚本
清单 10 loadScript()函数使用方法
1
2
3
|
loadScript("script1.js", function(){
alert("File is loaded!");
});
|
您可以在页面中动态加载很多 JavaScript
文件,但要注意,浏览器不保证文件加载的顺序。所有主流浏览器之中,只有
Firefox 和 Opera
保证脚本按照您指定的顺序执行。其他浏览器将按照服务器返回它们的次序下载并运行不同的代码文件。您可以将下载操作串联在一起以保证他们的次序,如下:
}
由于每个<script>标签初始下载时都会阻塞页面渲染,所以减少页面包含的<script>标签数量有助于改善这一情况。这不仅针对外链脚本,内嵌脚本的数量同样也要限制。浏览器在解析
HTML
页面的过程中每遇到一个<script>标签,都会因执行脚本而导致一定的延时,因此最小化延迟时间将会明显改善页面的总体性能。
清单 11 通过 loadScript()函数加载多个 JavaScript 脚本
1
2
3
4
5
6
7
|
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。虽然此方法可行,但如果要下载和执行的文件很多,还是有些麻烦。如果多个文件的次序十分重要,更好的办法是将这些文件按照正确的次序连接成一个文件。独立文件可以一次性下载所有代码(由于这是异步进行的,使用一个大文件并没有什么损失)。
动态脚本加载是非阻塞 JavaScript
下载中最常用的模式,因为它可以跨浏览器,而且简单易用。
// js同步加载
这个问题在处理外链 JavaScript 文件时略有不同。考虑到 HTTP
请求会带来额外的性能开销,因此下载单个 100Kb 的文件将比下载 5 个 20Kb
的文件更快。也就是说,减少页面中外链脚本的数量将会改善性能。
使用 XMLHttpRequest(XHR)对象
此技术首先创建一个 XHR 对象,然后下载 JavaScript 文件,接着用一个动态
<script>
元素将 JavaScript 代码注入页面。清单 12 是一个简单的例子:
function getScripts(i, linkArray, fn) {
通常一个大型网站或应用需要依赖数个 JavaScript
文件。您可以把多个文件合并成一个,这样只需要引用一个<script>标签,就可以减少性能消耗。文件合并的工作可通过离线的打包工具或者一些实时的在线服务来实现。
清单 12 通过 XHR 对象加载 JavaScript 脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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>
元素被添加到文档,代码将被执行,并准备使用。
这种方法的主要优点是,您可以下载不立即执行的 JavaScript
代码。由于代码返回在<script>
标签之外(换句话说不受<script>
标签约束),它下载后不会自动执行,这使得您可以推迟执行,直到一切都准备好了。另一个优点是,同样的代码在所有现代浏览器中都不会引发异常。
此方法最主要的限制是:JavaScript 文件必须与页面放置在同一个域内,不能从
CDN 下载(CDN 指”内容投递网络(Content Delivery
Network)”,所以大型网页通常不采用 XHR 脚本注入技术。
env || getEnv();
需要特别提醒的是,把一段内嵌脚本放在引用外链样式表的<link>之后会导致页面阻塞去等待样式表的下载。这样做是为了确保内嵌脚本在执行时能获得最精确的样式信息。因此,建议不要把内嵌脚本紧跟在<link>标签后面。
总结
减少 JavaScript 对性能的影响有以下几种方法:
- 将所有的
<script>
标签放到页面底部,也就是</body>
闭合标签之前,这能确保在脚本执行前页面已经完成了渲染。 - 尽可能地合并脚本。页面中的
<script>
标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。 - 采用无阻塞下载 JavaScript 脚本的方法:
- 使用
<script>
标签的 defer 属性(仅适用于 IE 和 Firefox 3.5
以上版本); - 使用动态创建的
<script>
元素来下载并执行代码; - 使用 XHR 对象下载 JavaScript 代码并注入页面中。
- 使用
通过以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web
网站和应用的实际性能。
var script = document.createElement(‘script’);
无阻塞的脚本
相关主题
- Steve Sounders
撰写的高性能网站建设指南介绍了网站性能问题的现状、产生的原因,以及改善或解决性能问题的原则、技术技巧和最佳实践。 - Steve Sounders
撰写的高性能网站建设进阶指南提供了提升网站性能的最佳实践和实用建议。 - 参考了雅虎特别性能小组在
JavaScript 影响页面下载性能方面的研究成果。 - Steve Sounders 的个人博客 HIGH PERFORMANCE WEB SITES
BLOG - “提升 web
应用程序的性能”(developerWorks,2011
年 8 月):找出瓶颈,加快客户端内容的速度。 - “全面提升 Web 2.0 应用程序的性能,第 3 部分:
浏览器渲染时间分析”
(developerWorks,2010 年 2 月):浏览器端页面渲染时间的性能分析。 - developerWorks Web
development专区:通过专门关于
Web 技术的文章和教程,扩展您在网站开发方面的技能。 - developerWorks Ajax
资源中心:这是有关 Ajax
编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki
和新闻。任何 Ajax 的新信息都能在这里找到。 - developerWorks Web 2.0
资源中心,这是有关 Web
2.0 相关信息的一站式中心,包括大量 Web 2.0
技术文章、教程、下载和相关技术资源。您还可以通过 Web 2.0
新手入门
栏目,迅速了解 Web 2.0 的相关概念。 - 查看 HTML5
专题,了解更多和
HTML5 相关的知识和动向。
script.type = ‘text/javascript’;
减少 JavaScript 文件大小并限制 HTTP 请求数在功能丰富的 Web
应用或大型网站上并不总是可行。Web 应用的功能越丰富,所需要的 JavaScript
代码就越多,尽管下载单个较大的 JavaScript 文件只产生一次 HTTP
请求,却会锁死浏览器的一大段时间。为避免这种情况,需要通过一些特定的技术向页面中逐步加载
JavaScript 文件,这样做在某种程度上来说不会阻塞浏览器。
script.src = linkArray[i];
无阻塞脚本的秘诀在于,在页面加载完成后才加载 JavaScript
代码。这就意味着在 window 对象的
onload事件触发后再下载脚本。有多种方式可以实现这一效果。
var head = document.head || document.getElementsByTagName(‘head’)[0];
延迟加载脚本
head.appendChild(script); if (env.ie && ‘onreadystatechange’ in
script && !(‘draggable’ in script)){ //ie浏览器使用以下方式加载
HTML 4 为<script>标签定义了一个扩展属性:defer。Defer
属性指明本元素所含的脚本不会修改 DOM,因此代码能安全地延迟执行。defer
属性只被 IE 4 和 Firefox 3.5
更高版本的浏览器所支持,所以它不是一个理想的跨浏览器解决方案。在其他浏览器中,defer
属性会被直接忽略,因此<script>标签会以默认的方式处理,也就是说会造成阻塞。然而,如果您的目标浏览器支持的话,这仍然是个有用的解决方案。清单
4 是一个例子
script.onreadystatechange = function () { if
(/loaded|complete/.test(script.readyState)) {
清单 4 defer 属性使用方法示例
script.onreadystatechange = null; if(i ===
linkArray.length-1) { if (fn) {
<script type="text/javascript" src="script1.js" defer></script>
fn();
带有 defer 属性的<script>标签可以放置在文档的任何位置。对应的
JavaScript
文件将在页面解析到<script>标签时开始下载,但不会执行,直到 DOM
加载完成,即onload事件触发前才会被执行。当一个带有 defer 属性的
JavaScript
文件下载时,它不会阻塞浏览器的其他进程,因此这类文件可以与其他资源文件一起并行下载。
}
任何带有 defer 属性的<script>元素在 DOM
完成加载之前都不会被执行,无论内嵌或者是外链脚本都是如此。清单 5
的例子展示了defer属性如何影响脚本行为:
} else {
清单 5 defer
属性对脚本行为的影响
getScripts(++i, linkArray, fn);
<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 就很有可能出现错误。
}else{
动态脚本元素
script.onload = function() { if(i === linkArray.length-1) {
if (fn) {
文档对象模型(DOM)允许您使用 JavaScript 动态创建 HTML
的几乎全部文档内容。<script>元素与页面其他元素一样,可以非常容易地通过标准
DOM 函数创建:
fn();
清单 6 通过标准 DOM
函数创建<script>元素
}
var script = document.createElement ("script");
script.type = "text/javascript";
script.src = "script1.js";
document.getElementsByTagName("head")[0].appendChild(script);
} else {
新的<script>元素加载 script1.js
源文件。此文件当元素添加到页面之后立刻开始下载。此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面处理过程。您甚至可以将这些代码放在<head>部分而不会对其余部分的页面代码造成影响(除了用于下载文件的
HTTP 连接)。
getScripts(++i, linkArray, fn);
当文件使用动态脚本节点下载时,返回的代码通常立即执行(除了 Firefox 和
Opera,他们将等待此前的所有动态脚本节点执行完毕)。当脚本是“自运行”类型时,这一机制运行正常,但是如果脚本只包含供页面其他脚本调用调用的接口,则会带来问题。这种情况下,您需要跟踪脚本下载完成并是否准备妥善。可以使用动态
<script> 节点发出事件得到相关信息。
}
Firefox、Opera, Chorme 和 Safari
3+会在<script>节点接收完成之后发出一个 onload
事件。您可以监听这一事件,以得到脚本准备好的通知:
};
清单 7 通过监听 onload 事件加载
JavaScript 脚本
}
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 有五种取值:
// js存在依赖关系 依次加载
1、“uninitialized”:默认状态
getScripts(0, [ ”,
‘], function() {
2、“loading”:下载开始
alert(‘callback’);
3、“loaded”:下载完成
});
4、“interactive”:下载完成但尚不可用
原文链接:http://www.kubiji.cn/juhe-id5905.html
5、“complete”:所有数据已经准备好
微软文档上说,在<script>元素的生命周期中,readyState
的这些取值不一定全部出现,但并没有指出哪些取值总会被用到。实践中,我们最感兴趣的是“loaded”和“complete”状态。Internet
Explorer 对这两个 readyState
值所表示的最终状态并不一致,有时<script>元素会得到“loader”却从不出现“complete”,但另外一些情况下出现“complete”而用不到“loaded”。最安全的办法就是在
readystatechange 事件中检查这两种状态,并且当其中一种状态出现时,删除
readystatechange 事件句柄(保证事件不会被处理两次):
清单 8 通过检查 readyState 状态加载
JavaScript 脚本
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 实现所需的功能:
清单 9 通过函数进行封装
function loadScript(url, callback){
var script = document.createElement ("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
此函数接收两个参数:JavaScript 文件的 URL,和一个当 JavaScript
接收完成时触发的回调函数。属性检查用于决定监视哪种事件。最后一步,设置
src 属性,并将<script>元素添加至页面。此 loadScript()
函数使用方法如下:
清单 10 loadScript()函数使用方法
loadScript("script1.js", function(){
alert("File is loaded!");
});
您可以在页面中动态加载很多 JavaScript
文件,但要注意,浏览器不保证文件加载的顺序。所有主流浏览器之中,只有
Firefox 和 Opera
保证脚本按照您指定的顺序执行。其他浏览器将按照服务器返回它们的次序下载并运行不同的代码文件。您可以将下载操作串联在一起以保证他们的次序,如下:
清单 11 通过 loadScript()函数加载多个
JavaScript 脚本
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。虽然此方法可行,但如果要下载和执行的文件很多,还是有些麻烦。如果多个文件的次序十分重要,更好的办法是将这些文件按照正确的次序连接成一个文件。独立文件可以一次性下载所有代码(由于这是异步进行的,使用一个大文件并没有什么损失)。
动态脚本加载是非阻塞 JavaScript
下载中最常用的模式,因为它可以跨浏览器,而且简单易用。
使用 XMLHttpRequest(XHR)对象
此技术首先创建一个 XHR 对象,然后下载 JavaScript 文件,接着用一个动态
<script> 元素将 JavaScript 代码注入页面。清单 12
是一个简单的例子:
清单 12 通过 XHR 对象加载 JavaScript
脚本
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>元素被添加到文档,代码将被执行,并准备使用。
这种方法的主要优点是,您可以下载不立即执行的 JavaScript
代码。由于代码返回在<script>标签之外(换句话说不受<script>标签约束),它下载后不会自动执行,这使得您可以推迟执行,直到一切都准备好了。另一个优点是,同样的代码在所有现代浏览器中都不会引发异常。
此方法最主要的限制是:JavaScript 文件必须与页面放置在同一个域内,不能从
CDN 下载(CDN 指”内容投递网络(Content Delivery
Network)”,所以大型网页通常不采用 XHR 脚本注入技术。
补充几个平时自己在用的函数
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');
});
总结
减少 JavaScript 对性能的影响有以下几种方法:
将所有的<script>标签放到页面底部,也就是</body>闭合标签之前,这能确保在脚本执行前页面已经完成了渲染。
尽可能地合并脚本。页面中的<script>标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。
采用无阻塞下载 JavaScript 脚本的方法:
使用<script>标签的 defer 属性(仅适用于 IE 和 Firefox 3.5
以上版本);
使用动态创建的<script>元素来下载并执行代码;
使用 XHR 对象下载 JavaScript 代码并注入页面中。
通过以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web
网站和应用的实际性能。
以上就是JavaScript性能优化总结之加载与执行的全部内容,希望对大家有所帮助。
您可能感兴趣的文章:
- 编写高性能的JavaScript
脚本的加载与执行 - Javascript
加载和执行-性能提高篇 - 实现高性能JavaScript之执行与加载
- 高性能的javascript之加载顺序与执行原理篇