ASP.NET站点性能提升-提高JavaScript加载
问题:JavaScript加载阻塞页面渲染
JavaScript是静态文件,和图片和CSS文件一样。但是,与图片不同,当使用<script>标签加载或执行JavaScript文件,页面渲染会暂停。
页面可能包含<script>后的依赖于<script>脚本块。如果加载JavaScript文件不阻塞页面渲染,其它脚本块可能在加载前已经执行了,导致标本错误。
方法1:在其它组件后开始加载
这个方法的是通过并行加载JavaScript和CSS文件、图像,而不是在JavaScript后,达到尽快渲染页面的目标。这样,当JavaScript完成加载时,CSS和图像也完成了加载,或至少减少在JavaScript加载后需要加载它们的时间。
为了并行加载JavaScript和CSS、图像,需要在开始加载JavaScript前加载它们。对于CSS文件很简单,将<link>标签放在<script>标签前:
<link rel="Stylesheet" type="text/css" href="css/style1.css" /> <script type="text/javascript" src="js/script1.js"></script>
开始加载图片需要一点技巧,因为图片通常在页面body中,而不是在页面head中。
<script type="text/javascript"> var img1 = new Image(); img1.src = "images/chemistry.png"; </script> <link rel="Stylesheet" type="text/css" href="css/style1.css" /> <script type="text/javascript" src="js/script1.js"></script>
第二个方法:
<body>
<div style="display:none">
<img src="images/chemistry.png" />
</div>
<script type="text/javascript" src="js/script1.js"></script>
也可以预加载多个图片。首先加载最重要的图片。这样当页面渲染时,它们可能会立刻显示。
<script type="text/javascript"> var img1 = new Image(); img1.src = "images/important.png"; var img1 = new Image(); img2.src = "images/notsoimportant.png"; var img1 = new Image(); img3.src = "images/unimportant.png"; </script>
方法2:更快地加载 JavaScript
图片使用的技术
JavaScript是静态文件,就像图片和CSS文件。所以许多应用于图片的技术也可以应用于JavaScript,包括无cookie子域、缓存和加速并行加载。
免费内容发布网络
- Google AJAX Libraries API
http://code.google.com/apis/ajaxlibs/ - Microsoft Ajax Content Delivery Network
http://www.asp.net/ajaxlibrary/cdn.ashx - jQuery CDN
http://docs.jquery.com/Downloading_jQuery
在ASP.NET 4.0及更高版本中,可以使用ScriptManager控件从Microsoft AJAX CDN加载ASP.NET AJAX脚本文件。设置EnableCdn属性为true:
<asp:ScriptManager ID="ScriptManager1" EnableCdn="true" runat="server" />
GZip压缩
并不是所有的文件都能从压缩中受益,例如,JPEG、PNG和GIF文件,因为它们已经被压缩了。IIS 7配置文件applicationHost.config中包括了当开启静态压缩后,被压缩的mime类型:
<staticTypes> <add mimeType="text/*" enabled="true" /> <add mimeType="message/*" enabled="true" /> <add mimeType="application/javascript" enabled="true" /> <add mimeType="*/*" enabled="false" /> </staticTypes>
为了允许IIS分辨某种文件的mime类型,applicationHost.config包括了默认的从文件扩展名到mime类型的映射,包括:
<staticContent lockAttributes="isDocFooterFileName">
...
<mimeMap fileExtension=".js"
mimeType="application/x-javascript" />
...
</staticContent>
如果仔细看,会发现.js扩展名,默认映射到一个不能压缩的mime类型。
最简单的方法是修改站点的web.config:
<system.webServer>
<staticContent>
<remove fileExtension=".js" />
<mimeMap fileExtension=".js" mimeType="text/javascript" />
</staticContent>
</system.webServer>
注意:IIS7只要压缩“频繁”访问的文件。
缩小JavaScript文件
- Microsoft Ajax Minifer
http://www.asp.net/ajaxlibrary/AjaxMinDocumentation.ashx - Google Closure Compiler
http://code.google.com/closure/compiler/ - ShrinkSafe
http://www.dojotoolkit.org/reference-guide/shrinksafe/index.html - YUI Compressor
http://developer.yahoo.com/yui/compressor/ - Yahoo! UI Library: YUI Compressor for .Net
http://yuicompressor.codeplex.com/ - JSMin
http://www.crockford.com/javascript/jsmin.html - CompressorRater
http://compressorrater.thruhere.net/
HTTP handler
using System.IO;
using System.Web.UI.WebControls;
using System.Web.Caching;
using Yahoo.Yui.Compressor;
using System.Web.UI;
namespace MinifyingHttpHandler
{
public class JavaScriptHttpHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
const string cacheKeyPrefix = "js__";
string compressedText = null;
string uncompressedText = null;
try
{
Uri url = context.Request.Url;
string path = url.PathAndQuery;
string cacheKey = cacheKeyPrefix + path;
compressedText = (string)context.Cache[cacheKey];
if (compressedText == null)
{
string filePath = context.Server.MapPath(path);
uncompressedText = File.ReadAllText(filePath);
compressedText = JavaScriptCompressor.Compress(uncompressedText);
CacheDependency cd = new CacheDependency(filePath);
context.Cache.Insert(cacheKey, compressedText, cd);
}
}
catch{}
context.Response.AddHeader("Cache-Control", "public,max-age=31536000");
context.Response.Write(compressedText ?? (uncompressedText ?? ""));
}
}
}
在web.config配置handler
如果使用IIS 7的集成管道模式,只需要在system.webServer的handlers节中加入新的handler:
<configuration>
<system.webServer>
<handlers>
<add name="Minifying" verb="*" path="*.js"
type="MinifyingHttpHandler.JavaScriptHttpHandler,
MinifyingHttpHandler" resourceType="File"/>
</handlers>
</system.webServer>
</configuration>
如果使用IIS6或IIS7经典模式,加入httpHandlers:
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="*.js"
type="MinifyingHttpHandler.JavaScriptHttpHandler,
MinifyingHttpHandler" />
</httpHandlers>
</system.web>
</configuration>
开启动态文件压缩
使用ASP.NET产生JavaScript,需要开启动态文件压缩。
组合或分解
一个流行的减少JavaScript加载时间的方法是将多个JavaScript文件组合成单个文件,这样只需要加载和个大文件。另一个方法正相反,是将一个大的JavaScript文件分解成多个小文件,这样就可以并行加载了。
什么时候组合
使用IE6时,因为IE只能串行下载静态文件。
什么时候分解
使用IE7以上,和Firefox和Chrome时。
阻止304消息
当运行测试时,注意浏览器在缓存中存储JavaScript文件。所以,当按下Ctrl + F5刷新页面时,一些浏览器,包括Internet Explorer,会发送If-Modified-Sinced和If-None-Match头到服务器。如果文件没有修改,服务器会回复304消息,表示浏览器缓存中的文件还是最新的,而不是一个包括整个文件的200消息。
可以使用Fiddler开发代理工具(http://www.fiddler2.com/fiddler2)。它的过滤功能请允许使If-Modified-Sinced和If-None-Match头失效。
实现自动文件组合
实现自动文件组合的方法:
- ASP.NET ScriptManager控件
- ASP.NET中的压缩和HTML、CSS、JS缩小
- Combres 2.0
除了ASP.NET ScriptManager控件,这些方法也支持组合CSS文件。
ASP.NET ScriptManager控件
<asp:ScriptManager runat="server">
<CompositeScript>
<Scripts>
<asp:ScriptReference Path="js/script1.js" />
<asp:ScriptReference Path="js/script2.js" />
</Scripts>
</CompositeScript>
</asp:ScriptManager>
如果只使用ScriptManager组合自己的文件,而不加载ASP.NET Ajax库,按照以下方式使用ScriptManager指令:
- Using ScriptManager with other frameworks
http://weblogs.asp.net/bleroy/archive/2008/07/07/using-scriptmanager-with-other-frameworks.aspx
在ASP.NET中压缩和进行HTML、CSS、JS缩小
下载:
它的缺点是使用包含查询字符串的脚本和CSS文件地址,这会缓存功能。这些查询字符串包含组成组合文件的单个文件的名字,所以它们会很长。最大的缺点是它会在CSS文件在不同的目录并且使用相对路径时,会出现问题,例如:
background-image: url(../images/chemistry.png);
这是因为组合的CSS文件不能保证路径与原路径相同,所以相对路径不再指向同一位置。
Combres 2.0
下载:
这个方案最大的缺点是需要建立配置文件,当网站变化时,要修改配置文件。
移除不用的代码
有很多工具可以帮助识别不再使用的代码,但是要小心—JavaScript是高度动态的语言,所以工具也很难确保一段代码真正不被使用。
- JSLint
http://www.jslint.com/lint.html - Jsure
http://aurochs.fr/jsure.html - JSCoverage
http://siliconforks.com/jscoverage/
方法3:按需加载JavaScript
JavaScript分为两类—渲染页面的代码,处理用户界面事件的代码,例如按钮点击。
虽然渲染代码需要和页面一起加载和执行,用户界面代码可以延迟加载。
另一方面,这需要分离从渲染代码中用户界面代码。需要调用可能还没有加载的代码,告诉访问者代码正在加载,最后在加载后调用代码。
从渲染代码中分离用户界面代码
一个辨别哪些代码在加载页面时使用的工具是Page Speed,一个Firefox插件。
http://code.google.com/speed/page-speed/.
OndemandLoader library
// Only ever instantiate this object once on a page.
// That is, do NOT do this:
// var loader1 = new OnDemandLoader(scriptMap1, scriptDependencies1);
// var loader2 = new OnDemandLoader(scriptMap2, scriptDependencies2);
function OnDemandLoader(scriptMap, scriptDependencies) {
// ---------------------------------------------------
// Private functions and fields
// Shows for each function which script file has that function.
var _scriptMap = scriptMap;
// Shows for each script file which other script files
// need to be loaded to make it work. Also contains a test symbol
// that will be present after the script file has loaded.
var _scriptDependencies = scriptDependencies;
// Store the this pointer to the loader object. Need it to find out whether
// event handlers were attached. It is ok to do this here, because this class
// is only ever instantiated once.
var _thisObj = this;
// The task list holds all outstanding tasks - essentially the functions
// that need to be executed.
var _taskList = new Array();
// Shows a "loading ..." box for a control.
var showLoading = function(control) {
if (((typeof (control)) == 'object') &&
(control != null) &&
((typeof (control.offsetLeft)) == 'number')) {
var loadingElem = document.createElement('div');
loadingElem.style.position = 'absolute';
loadingElem.style.left = (control.offsetLeft + 5) + 'px';
loadingElem.style.top = (control.offsetTop + 5) + 'px';
loadingElem.className = 'loading';
var loadingTextElem = document.createTextNode('Loading ...');
loadingElem.appendChild(loadingTextElem);
var docBody = document.getElementsByTagName("body")[0];
docBody.appendChild(loadingElem);
control.loadingElem = loadingElem;
}
}
// Hides a "loading ..." box that is being shown for a control.
var hideLoading = function(control) {
if ((control != null) && (control.loadingElem)) {
control.loadingElem.parentNode.removeChild(control.loadingElem);
control.loadingElem = null;
}
}
// Returns the path of the script that defines the given function.
// Returns empty string if there is no script associated with the given function.
var functionSource = function(fname) {
var i = _scriptMap.length - 1;
for (; i >= 0; i--) {
if (_scriptMap[i].fname == fname) {
return _scriptMap[i].src;
}
}
return '';
}
// Returns true if longString ends in shortString (or is equal).
// For example, 'abcdef' ends in 'def'.
var endsWith = function(longString, shortString) {
var longLen = longString.length;
var shortLen = shortString.length;
if (longLen < shortLen) {
return false;
}
return (longString.substring(longLen - shortLen) == shortString);
}
// Determines whether there is a script tag with the given script source.
// If such a script tag exists, returns that script tag.
// If no such script tag exists, returns null.
var scriptWithSrc = function(src) {
var scriptTags = document.getElementsByTagName('script');
var scriptTagsLen = scriptTags.length;
for (var i = 0; i < scriptTagsLen; i++) {
if (endsWith(scriptTags[i].src, src)) {
return scriptTags[i];
}
}
return null;
}
// Returns true if one of the array elements in _taskList
// matches task. To match, they need to have the same
// function name and refer to the same object.
var taskExists = function(task) {
var taskListLen = _taskList.length;
for (var i = 0; i < taskListLen; i++) {
if ((_taskList[i].fname == task.fname) &&
(_taskList[i].thisObj == task.thisObj)) {
return true;
}
}
return false;
}
// Returns true if all scripts listed in scriptDependencies
// have loaded.
var allScriptsLoaded = function() {
var i = _scriptDependencies.length - 1;
for (; i >= 0; i--) {
if (eval('typeof ' + _scriptDependencies[i].testSymbol) == 'undefined') {
return false;
}
}
return true;
}
var checkEventOnallscriptsloaded = function() {
if ((typeof (_thisObj.onallscriptsloaded) == 'function') &&
allScriptsLoaded()) {
_thisObj.onallscriptsloaded();
}
}
// Executed after a script tag has loaded.
// Executes all tasks where the functions are now available.
var onScriptLoaded = function() {
var i = 0;
while (i < _taskList.length) {
var task = _taskList[i];
var fname = task.fname;
var allSymbolsDefined = functionExists(fname);
var testSymbols = task.testSymbols;
var j = testSymbols.length - 1;
for (; (j >= 0) && allSymbolsDefined; j--) {
allSymbolsDefined = (eval('typeof ' + testSymbols[j]) != 'undefined');
}
if (allSymbolsDefined) {
// The function is available, and all scripts that are required
// have been loaded. Execute it.
// Remove task from the array.
// By removing the task, it doesn't get executed twice.
_taskList.splice(i, 1);
hideLoading(task.thisObj);
checkEventOnallscriptsloaded();
var fref = eval(fname);
fref.apply(task.thisObj, task.args);
}
else {
// function not available. Go to next task.
i++;
}
}
}
// Attaches onload handler to a script element.
var attachOnLoad = function(scriptElem) {
scriptElem.onload = onScriptLoaded;
scriptElem.onreadystatechange = function() {
if ((scriptElem.readyState === "loaded") ||
(scriptElem.readyState === "complete")) {
onScriptLoaded();
}
}
}
// Returns record describing the given script
// from _scriptDependencies.
var getScriptRecord = function(src) {
var i = _scriptDependencies.length - 1;
for (; i >= 0; i--) {
if (_scriptDependencies[i].src == src) {
return _scriptDependencies[i];
}
}
return new Array();
}
// Returns in array testSymbols all function names that need
// to be defined to conclude that the script with the given src
// has loaded.
//
// If a script is not dependent on other scripts, testSymbols will
// have one entry. If it is dependent on say 3 scripts, it will contain
// 4 entries (one for each script that needs to have loaded).
var getTestSymbols = function(src, testSymbols) {
// find the scripts that this script depends on
var scriptRecord = getScriptRecord(src);
var dependentOn = scriptRecord.dependentOn;
var testSymbol = scriptRecord.testSymbol;
// Visit all scripts that this script is dependent on
var i = dependentOn.length - 1;
for (; i >= 0; i--) {
getTestSymbols(dependentOn[i], testSymbols);
}
testSymbols.push(testSymbol);
}
// Makes sure that script with given src is loaded.
// After the script has loaded, the function whose name is in fname should have been
// added. That function will then be executed, with thisObj as the this pointer
// and the arguments passed in args.
var ensureScriptLoad = function(src, fname, thisObj, args) {
// Create a task object that has the name of the function to call,
// its this pointer, and the arguments to pass to the function.
var task = new Object();
task.fname = fname;
task.thisObj = thisObj;
task.args = args;
var testSymbols = new Array();
getTestSymbols(src, testSymbols);
task.testSymbols = testSymbols;
// Add the task object to the _taskList array.
// That way, the function will be executed after the
// script has loaded.
if (!taskExists(task)) {
_taskList.push(task);
showLoading(thisObj);
}
// Load the script, and any scripts that need to be loaded as well
// so the script runs properly.
loadScript2(src);
}
// Ensures the script, and all the scripts it depends
// on, are loaded.
var loadScript2 = function(src) {
// find the scripts that this script depends on
var scriptRecord = getScriptRecord(src);
var dependentOn = scriptRecord.dependentOn;
// Load all scripts that this script is dependent on,
// by recurrently calling this function.
var i = dependentOn.length - 1;
for (; i >= 0; i--) {
loadScript2(dependentOn[i]);
}
// Check whether the script is already being loaded.
// If not, create a new script tag so it starts loading.
// Otherwise, make sure the onload handlers are attached.
var scriptElem = scriptWithSrc(src);
if (scriptElem == null) {
var scriptElem = document.createElement('script');
scriptElem.src = src;
// attach handlers before starting the download. That way, you will never
// miss the onload event.
attachOnLoad(scriptElem);
document.getElementsByTagName('head')[0].appendChild(scriptElem);
}
else {
attachOnLoad(scriptElem);
}
}
// Returns true if a function with the given name
// exists, and is not a stub.
var functionExists = function(fname) {
var ftype;
ftype = eval('typeof ' + fname);
if (ftype != 'function') { return false; }
var fref = eval(fname);
if (fref.stub) { return false; }
return true;
}
// ---------------------------------------------------
// Public functions
// Ensures the script, and all the scripts it depends
// on, are loaded.
this.loadScript = function(src) {
loadScript2(src);
}
// Takes the name of a function to execute (as a string),
// followed by the pointer to the control that the function is called for,
// followed by
// the arguments to the function. Accesses the arguments via the
// arguments array.
this.runf = function(fname, thisObj) {
var args = []; // empty array
// copy all other arguments we want to pass on to the function
// whos name is in fname. The first 2 arguments passed to runf
// are fname and thisObj, the rest of the arguments will be
// the arguments to be passed on to the function.
for (var i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
// If the function exists (ftype equals 'function'), execute it
// by calling apply. Otherwise, call ensureScriptLoad, which ensures
// that the script that contains the function is loaded, and the function
// then executed.
if (functionExists(fname)) {
var fref = eval(fname);
checkEventOnallscriptsloaded();
fref.apply(thisObj, args);
}
else {
var src = functionSource(fname);
if (src == '') { alert('No script for function ' + fname); }
ensureScriptLoad(src, fname, thisObj, args)
}
}
}
OnDemanderLoader的一个缺点是总是并行加载所有需要的脚本。如果一个脚本自动执行在另一个脚本中定义的函数,如果另一个脚本还没有加载,会报一个JavaScript错误,但是,如果库脚本文件只定义了函数和其它对象,OnDemanderLoader会工作地很好。
初始化OnDemanderLoader
示例代码中定义了两个数组:脚本映射数组和脚本依赖数组。脚本映射数组说明了脚本文件:
var scriptMap = [
{ fname: 'btn1a_click', src: 'js/Button1Code.js' },
{ fname: 'btn1b_click', src: 'js/Button1Code.js' },
{ fname: 'btn2_click', src: 'js/Button2Code.js' }
];
函数btn1a_click和btn1b_click在js/Button1Code.js中定义,btn2_click在js/Button2Code.js中定义。
第二个数组定义了运行每个脚本文件需要的其它脚本文件:
var scriptDependencies = [
{
src: '/js/Button1Code.js',
testSymbol: 'btn1a_click',
dependentOn: ['/js/UILibrary1.js', '/js/UILibrary2.js']
},
{
src: '/js/Button2Code.js',
testSymbol: 'btn2_click',
dependentOn: ['/js/UILibrary2.js']
},
{
src: '/js/UILibrary2.js',
testSymbol: 'uifunction2',
dependentOn: []
},
{
src: '/js/UILibrary1.js',
testSymbol: 'uifunction1',
dependentOn: ['/js/UILibrary2.js']
}
];
Button1Code.js依赖UILibrary1.js和UILibrary2.js。Button2Code.js依赖UILibrary2.js。UILibrary1.js依赖UILibrary2.js,UILibrary2.js不需要其它任何脚本文件。
构造loader对象:
<script type="text/javascript" src="js/OnDemandLoader.js"> </script> var loader = new OnDemandLoader(scriptMap, scriptDependencies);
调用未加载函数
有两种方法调用未加载的函数:
- 调用loader函数。
- 调用stud函数。
第一种方法。OnDemandLoader对象暴露了一个加载函数runf,它接收调用的函数名、调用参数和当前this指针作为参数:
function runf(fname, thisObj) {
// implementation
}
例如:
<input id="btn1a" type="button" value="Button 1a" onclick="loader.runf('btn1a_click', this, this.value, 'more info')" />
如果按钮的处理函数是编程赋予的,那么:
<input id="btn1b" type="button" value="Button 1b" />
<script type="text/javascript">
window.onload = function() {
document.getElementById('btn1b').onclick = function() {
loader.runf('btn1b_click', this);
}
}
</script>
预加载
现在,当用户点击一个按钮时,处理点击的代码还没有加载。如果代码加载需要很多时间,这就是一个问题了。
一个解决方法是在页面加载后初始化用户界面代码,而不是当用户界面事件触发时。
可以使用OnDemandLoader对象的loadScript函数实现预加载。这个函数加载JavaScript文件和它依赖的文件,而不会阻塞页面渲染。
<script type="text/javascript">
window.onload = function() {
document.getElementById('btn1b').onclick = btn1b_click;
loader.loadScript('js/Button1Code.js');
loader.loadScript('js/Button2Code.js');
}
</script>
方法4:无阻塞加载JavaScript
这个方法的思想是加载所有的脚本文件而不阻塞页面渲染。
移动所有的<script>标签到页面尾部
将用户界面代码和渲染代码分离
渲染页面的代码一般要比处理用户界面事件的代码小得多。
使用Firefox的Page Speed插件可以找出哪些JavaScript函数不是渲染页面所必须的。
<head runat="server">
<script type="text/javascript" src="js/Beautify.js"></script>
</head>
<body>
... page contents
<script type="text/javascript">
beautify(); // run code that helps render the page
</script>
<script type="text/javascript" src="js/UICode.js"></script>
<script type="text/javascript">
attachEventHandlers();
</script>
</body>
页面加载指示
<div id="pageloading" style="position:fixed; top: 10px; left: 50%;" >Loading ...</div>
<script type="text/javascript">
beautify();
function disableButtons(disable) {
var inputTags = document.getElementsByTagName('input');
var inputTagsLen = inputTags.length;
for (var i = 0; i < inputTagsLen; i++) {
inputTags[i].disabled = disable;
}
}
disableButtons(true); // Disable all input elements
</script>
<script type="text/javascript">
attachEventHandlers();
document.getElementById('pageloading').style.display = 'none';
disableButtons(false); // Re-enable all input elements
</script>
与页面同步加载代码
在前面的解决方案中,用户界面代码加载是在页面已经加载完成并渲染后初始化的。
但是,如果页面的HTML需要很长时间加载,可能需要在页面开始时启动加载,这样代码加载是与HTML同步加载的。
可以使用OnDemandLoader对象实现这个功能。可以在页面加载时加载一个或多个脚本文件集,在每个脚本集加载完成后,都可以调用一个方法。最后,它暴露了一个onallscriptsloaded事件,当所有脚本文件都加载完成后触发,可以用来移除页面加载指示。
初始化loader对象
var scriptMap = [
{ fname: 'attachEventHandlers', src: 'js/UICode.js' },
{ fname: 'beautify', src: 'js/Beautify.js' }
];
var scriptDependencies = [
{
src: 'js/UICode.js',
testSymbol: 'attachEventHandlers',
dependentOn: []
},
{
src: 'js/Beautify.js',
testSymbol: 'beautify',
dependentOn: []
}
];
<script type="text/javascript" src="js/OnDemandLoader.js">
</script>
var loader = new OnDemandLoader(scriptMap, scriptDependencies);
当页面加载时,加载代码
<body>
<div id="pageloading" ...>Loading ...</div>
<script type="text/javascript">
loader.onallscriptsloaded = function() {
document.getElementById('pageloading').style.display =
'none';
disableButtons(false);
}
loader.runf('beautify', null);
loader.runf('attachEventHandlers', null);
</script>
保证页面渲染后运行代码
最后一个问题,脚本文件可能在页面完成渲染前已经加载结束了。这样,代码就可能更新页面或者关联事件处理程序失败。
解决这个问题的方法是在它们加载后,和页面渲染结束后,都进行调用。
try { attachEventHandlers(); } catch (err) { }
try { beautify(); } catch (err) { }
如何知道页面渲染完成?常用的方法包括:
- 创建页面的onload事件。这是最常用的方法,但有个大问题。当OnDemandLoader对象开始加载脚本文件,它会向DOM中插入一个<script>标签:
var se = document.createElement('script'); se.src = src; document.getElementsByTagName('head')[0].appendChild(se);这个方法加载脚本文件,而不会阻塞页面渲染,除了在FireFox,它会阻塞onload事件。这意味着如果渲染代码加载很快,用户界面代码花费很长时间,渲染代码依然会被延迟,直到用户界面代码完成加载。
-
将<script>标签放在包含attachEventHandlers和beautify的页面的尾部。不幸的是,Firefox不仅阻塞onload,还会阻塞所有的script标签,直到所有的代码加载完成。
-
在页面的最后部放置一个隐藏元素,定时检查那个元素是否存在。如果存在,整个页面加载完成。
可以使用XMLHttpRequest而不是在DOM中插入<script>标签异步加载JavaScript代码的方式使用前两个方法。但是,这样就不能从其它主机上加载脚本文件了。例如,不能从Google CDN上加载JQuery库。
在这个例子中,使用第三种方法。
在页面结尾放置一个隐藏元素:
<div id="lastelement" style="display: none;"></div> </body>
在页面开始处运行检查代码:
function pollpage() {
var lastelement = document.getElementById('lastelement');
if (lastelement == null) {
setTimeout("pollpage();", 100);
return;
}
try { attachEventHandlers(); } catch (err) { }
try { beautify(); } catch (err) { }
if (document.getElementById('pageloading').
style.display != 'none') {
disableButtons(true);
}
}
pollpage();
提升广告加载
如果使用的广告网络,例如DoubleClick或Google AdWords,它们会提供一段代码放在页面上:
<script src="http://adserver.js?....."></script>
通常这没问题。但是,有时广告服务器会变慢,就会延迟广告下的页面。
解决这处问题的方法是在事件现在渲染完成后加载广告。
<div id="ad" style="width: 486px; height: 60px; background: #ffffff url('/images/throbber.gif') no-repeat center;">
</div>
<script type="text/javascript">
var scriptloaded = false;
var divloaded = false;
function adloaded() {
...
}
</script>
<div id="tempad" style="display:none">
<script type="text/javascript" src="http://adserver.js?....."
onload="scriptloaded=true; adloaded()"
onreadystatechange="if (this.readyState=='complete') {
scriptloaded=true; adloaded(); }">
</script>
</div>
<script type="text/javascript">
divloaded = true;
adloaded();
</script>
function adloaded() {
if ((!scriptloaded) || (!divloaded)) { return; }
var et = document.getElementById("tempad");
et.parentNode.removeChild(et);
var d = document.getElementById("ad");
d.appendChild(et);
et.style.display = "block";
}
提升CSS加载
移除未使用的CSS选择器
使用Firefox插件 https://addons.mozilla.org/en-US/firefox/addon/5392/。
加载CSS而不阻塞渲染
<script type="text/javascript">
var scriptElem = document.createElement('link');
scriptElem.type = "text/css";
scriptElem.rel = "Stylesheet";
scriptElem.href = "css/style1.css";
document.getElementsByTagName('head')[0].appendChild(scriptElem);
</script>
使用这个方法时,页面首先加载。当CSS加载完成后,改变页面外观。这可能不是你想要的效果,即使页面渲染时间更短。
更多资源
- What ASP.NET Developers Should Know About JavaScript
http://odetocode.com/Articles/473.aspx - Function.apply and Function.call in JavaScript
http://odetocode.com/Blogs/scott/archive/2007/07/05/function-apply-and-function-call-in-javascript.aspx - JavaScript and HTML DOM Reference
http://www.w3schools.com/jsref/default.asp - Ecmascript reference
http://www.devguru.com/Technologies/ecmascript/quickref/javascript_index.html - JavaScript Kit - JavaScript Tutorials
http://www.javascriptkit.com/javatutors/ - Object Oriented Programming in JavaScript
http://mckoss.com/jscript/object.htm - QuirksMode – JavaScript
http://www.quirksmode.org/js/contents.html - HowToCreate - JavaScript Tutorial
http://www.howtocreate.co.uk/tutorials/javascript/ - Microsoft CDN for JQuery or Google CDN?
http://stackoverflow.com/questions/1447184/microsoft-cdn-for-jquery-or-google-cdn - Speeding up website load times: Using a public CDN for Javascript libraries such as jQuery
http://gem-session.com/2010/06/speeding-up-website-load-times-using-a-public-cdn-for-javascript-libraries-such-as-jquery - CompressorRater
http://compressorrater.thruhere.net/ - On-Demand Javascript
http://ajaxpatterns.org/On-Demand_Javascript

浙公网安备 33010602011771号