现在的位置: 首页 > 综合 > 正文

ASP.NET站点性能提升-提高JavaScript加载

2012年05月20日 ⁄ 综合 ⁄ 共 21484字 ⁄ 字号 评论关闭
文章目录

问题: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子域、缓存和加速并行加载。

免费内容发布网络

在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文件

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头失效。

2011-01-26 14 35 52

实现自动文件组合

实现自动文件组合的方法:

  • 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指令:

在ASP.NET中压缩和进行HTML、CSS、JS缩小

下载:

http://candrews.integralblue.com/2009/05/compression-deflate-and-html-css-js-minification-in-asp-net/

它的缺点是使用包含查询字符串的脚本和CSS文件地址,这会缓存功能。这些查询字符串包含组成组合文件的单个文件的名字,所以它们会很长。最大的缺点是它会在CSS文件在不同的目录并且使用相对路径时,会出现问题,例如:

background-image: url(../images/chemistry.png);

这是因为组合的CSS文件不能保证路径与原路径相同,所以相对路径不再指向同一位置。

Combres 2.0

下载:

http://combres.codeplex.com/

这个方案最大的缺点是需要建立配置文件,当网站变化时,要修改配置文件。

移除不用的代码

有很多工具可以帮助识别不再使用的代码,但是要小心—JavaScript是高度动态的语言,所以工具也很难确保一段代码真正不被使用。

方法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加载完成后,改变页面外观。这可能不是你想要的效果,即使页面渲染时间更短。

更多资源

抱歉!评论已关闭.