广

ASP编程

  • IOS开发
  • android开发
  • PHP编程
  • JavaScript
  • ASP.NET
  • ASP编程
  • JSP编程
  • Java编程
  • 易语言
  • Ruby编程
  • Perl编程
  • AJAX
  • 正则表达式
  • C语言
  • 编程开发

    Sys.ScriptLoader与JS加载进度条的实现

    2018-05-03 14:17:36 次阅读 稿源:互联网
    零七广告

      今天有人问我,163邮箱那样的Javascript加载进度条是如何实现的。

      我不知道,不过实现一个不难,因为<script />有onload和onreadystatechange。还有就是,我们有Atlas。

      Atlas中有个类:Sys.ScriptLoader,它的作用就是在页面中依次地加载多个Script文件。在实现之前,先来分析一下这个类的代码。

       

        1Sys.ScriptLoader = function() {
        2
        3    // 所有Script的reference对象数组。
        4    var _references;
        5    // 所有Script加载完之后执行的回调函数。
        6    var _completionCallback;
        7    // 执行回调函数时提供的上下文(参数)。
        8    var _callbackContext;
        9
       10    // 当前正在加载的Script的HTTP Element(<script />)。
       11    var _currentLoadingReference;
       12    // 当前的Script加载完成后所调用的回调函数。
       13    var _currentOnScriptLoad;
       14   
       15    // ScriptLoader唯一的方法,传入三个参数,参数含义不再赘述。
       16    this.load = function(references, completionCallback, callbackContext) {
       17        _references = references;
       18        _completionCallback = completionCallback;
       19        _callbackContext = callbackContext;
       20       
       21        loadReferences();
       22    }
       23
       24    // 开始加载引用。
       25    function loadReferences() {
       26        // 如果当前正在加载某个Script。
       27        // 这表示此方法不是第一次被调用,而是在某个Script被加载
       28        // 完成后才被调用,用以加载下一个Script。
       29        if (_currentLoadingReference) {
       30            // 查看当前Script元素的readyState,IE下为complete,
       31            // 其他浏览器如FF则为loaded(FF其实并无此属性,
       32            // 但是下面的代码会将其设为loaded)。
       33            // 如果加载失败,则退出。
       34            if ((_currentLoadingReference.readyState != 'loaded') &&
       35                (_currentLoadingReference.readyState != 'complete')) {
       36                return;
       37            }
       38            else {
       39                // 进入此分支,表明加载成功。
       40               
       41                // 如果当前Script定义了onLoad函数。
       42                if (_currentOnScriptLoad) {
       43                    // 通过eval调用(这里是个麻烦的地方)。
       44                    eval(_currentOnScriptLoad);
       45                    // 设为null,释放资源。
       46                    _currentOnScriptLoad = null;
       47                }
       48               
       49                // 将相关事件设为null以确保释放资源。
       50                if (Sys.Runtime.get_hostType() != Sys.HostType.InternetExplorer) {
       51                    // 如果当前浏览器不是IE,见下面的代码
       52                    // 会发现为<script />定义了onload事件。
       53                    _currentLoadingReference.onload = null;
       54                }
       55                else {
       56                    // 如果是IE,见下面代码会发现为了
       57                    // <script />定义了onreadystatechange事件。
       58                    _currentLoadingReference.onreadystatechange = null;
       59                }
       60               
       61                // 最终释放当前的<script />引用。
       62                _currentLoadingReference = null;
       63            }
       64        }
       65
       66        // 如果还有没有加载的Script。
       67        if (_references.length) {
       68            // 出队列。
       69            var reference = _references.dequeue();
       70            // 创建<script />
       71            var scriptElement = document.createElement('script');
       72            // 设当前的<script />和当前加载成功的回调函数。
       73            _currentLoadingReference = scriptElement;
       74            _currentOnScriptLoad = reference.onscriptload;
       75           
       76            if (Sys.Runtime.get_hostType() != Sys.HostType.InternetExplorer) {
       77                // 如果不是IE的话,那么为<script />设属性readyState,
       78                // 并且使用onload事件。
       79                scriptElement.readyState = 'loaded';
       80                scriptElement.onload = loadReferences;
       81            }
       82            else {
       83                // 如果是IE,那么使用onreadystatechange事件。
       84                scriptElement.onreadystatechange = loadReferences;
       85            }
       86            scriptElement.type = 'text/javascript';
       87            scriptElement.src = reference.url;
       88
       89            // 将<script />添加至DOM
       90            var headElement = document.getElementsByTagName('head')[0];
       91            headElement.appendChild(scriptElement);
       92
       93            return;
       94        }
       95       
       96        // 如果执行到这里,说明所有的Script已经加载完了。
       97        // 如果定义了所有Script加载完之后执行的回调函数,
       98        // 那么执行并释放资源。
       99        if (_completionCallback) {
      100            var completionCallback = _completionCallback;
      101            var callbackContext = _callbackContext;
      102           
      103            _completionCallback = null;
      104            _callbackContext = null;
      105           
      106            completionCallback(callbackContext);
      107        }
      108       
      109        _references = null;
      110    }
      111}
      112Sys.ScriptLoader.registerClass('Sys.ScriptLoader');
      可以看出,Sys.ScriptLoader加载script的方法就是通过代码依次向<header />里添加<script />元素。事实上,它在Atlas中被使用的非常少。

      事实上,Sys.ScriptLoader的代码非常简单,我添加的注释越看越像画蛇添足。值得注意的是所有的资源都被尽可能的释放。尤其注意从第99行开始的代码,if体内首先用临时变量保留两个全局变量,然后再将全局变量释放。其目的就是避免在completionCallback在执行时抛出异常而导致的内存泄露,即使只有万分之一的可能性。Javascript越多,则越容易造成内存泄露,在编写JS代码时最好注意这方面的问题。

      接着解释一下load方法的第一个参数references,原本以为这一个Sys.Reference类的数组,结果发现其实相差甚远。不管怎么样顺便看一下该类的代码。

       

       1Sys.Reference = function() {
       2
       3    var _component;
       4    var _onload;
       5   
       6    this.get_component = function() {
       7        return _component;
       8    }
       9    this.set_component = function(value) {
      10        _component = value;
      11    }
      12   
      13    this.get_onscriptload = function() {
      14        return _onload;
      15    }
      16    this.set_onscriptload = function(value) {
      17        _onload = value;
      18    }
      19   
      20    this.dispose = function() {
      21        _component = null;
      22    }
      23   
      24    this.getDescriptor = function() {
      25        var td = new Sys.TypeDescriptor();
      26       
      27        td.addProperty('component', Object);
      28        td.addProperty('onscriptload', String);
      29        return td;
      30    }
      31}
      32Sys.Reference.registerSealedClass('Sys.Reference', null, Sys.ITypeDescriptorProvider, Sys.IDisposable);
      33Sys.TypeDescriptor.addType('script', 'reference', Sys.Reference);
      关心一下Sys.ScriptLoader类的代码可知,reference数组的每个元素其实只是简单的“{ url : "http://www.sample.com/sample.js", onscriptload : "alert(1)"}”形式的对象。不过这样也好,想构造这么一个数组也能轻易地使用JSON了。

      到这里,我想大家也应该想到了如何使用Sys.ScriptLoader轻而易举地制作JS加载的进度条。不过既然写到了这里,也就继续把它进行一个简单的实现。

      首先是aspx文件。

       1<%@ Page Language="C#" %>
       2
       3<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
       4
       5<script runat="server">
       6
       7</script>
       8
       9<html xmlns="http://www.w3.org/1999/xhtml" >
      10<head runat="server">
      11    <title>Load Scripts</title>
      12    <script language="javascript">
      13        function Load()
      14        {
      15            document.getElementById("bar").style.width = "0px";
      16            var scripts = new Array();
      17            for (var i = 0; i < 8; i++)
      18            {
      19                var s = new Object();
      20                var sleep = Math.round((Math.random() * 400)) + 100;
      21                s.url = "Script.ashx?sleep=" + sleep + "&t=" + Math.random();
      22                s.cost = sleep;
      23                scripts.push(s);
      24            }
      25               
      26            Jeffz.Sample.LoadScripts.load(scripts);
      27        }
      28    </script>
      29</head>
      30<body style="font-family: Arial;">
      31    <form id="form1" runat="server">
      32    <div>
      33        <atlas:ScriptManager ID="ScriptManager1" runat="server">
      34            <Scripts>
      35                <atlas:ScriptReference Path="js/LoadScripts.js" />
      36            </Scripts>
      37        </atlas:ScriptManager>
      38
      39        Progress Bar:       
      40        <div style="border: solid 1px black;">
      41            <div id="bar" style="height: 20px; width:0%; background-color:Red;"></div>
      42        </div>
      43        <input type="button" onclick="Load()" value="Load" />
      44        <div id="message"></div>
      45    </div>
      46    </form>
      47</body>
      48</html>
      非常的简单。使用两个DIV制作了一个最简单的进度条。在点击按钮时调用了Load()函数。该函数随机生成了Script链接并生成了一个8元素的scripts数组。scripts数组的格式如下:

      1var scripts =
      2[
      3    { url : "http://www.sample.com/sample1.js", cost : costOfLoading1 },
      4    { url : "http://www.sample.com/sample2.js", cost : costOfLoading2 },
      5    { url : "http://www.sample.com/sample3.js", cost : costOfLoading3 }
      6];
      每个元素的url属性不必说,而cost的功能就是表示加载该文件所消耗的时间的值。这个值没有单位,用到的只是这个值在总共消耗里的比例。另外,可以看到有一个Script.ashx,其作用是模拟一个长时间script加载,它会根据querystring中的sleep的值将线程休眠一段时间(至于后面的t,目的只是通过改变querystring来避免点击按钮时浏览器的缓存),这个文件几乎没有代码,可以在范例下载中看到它的实现。最后通过调用Jeffz.Sample.LoadScripts.load方法进行加载,这就涉及到了下面的代码,LoadScripts.js:

       1Type.registerNamespace('Jeffz.Sample');
       2
       3Jeffz.Sample.LoadScripts = new function()
       4{
       5    var totalCost = 0;
       6    var scriptLoader = new Sys.ScriptLoader();
       7
       8    this.load = function(scripts)
       9    {
      10        if (Jeffz.Sample.__onScriptLoad != null)
      11        {
      12            throw new Error("In progress");
      13        }
      14       
      15        totalCost = 0;
      16        Jeffz.Sample.__onScriptLoad = onScriptLoad;
      17        var references = new Array();
      18   
      19        var loadedCost = 0;
      20        for (var i = 0; i < scripts.length; i++)
      21        {
      22            totalCost += scripts[i].cost;
      23            loadedCost += scripts[i].cost;
      24           
      25            var ref = createReference(scripts[i].url, loadedCost);
      26           
      27            references.push(ref);
      28        }
      29       
      30        scriptLoader.load(references, onComplete);
      31    }
      32   
      33    function createReference(url, loadedCost)
      34    {
      35        var ref = new Object();
      36        ref.url = url;
      37        ref.onscriptload = "Jeffz.Sample.__onScriptLoad('" + url + "', " + loadedCost + ")";
      38        return ref;
      39    }
      40   
      41    function onComplete()
      42    {
      43        Jeffz.Sample.__onScriptLoad = null;
      44    }
      45   
      46    function onScriptLoad(url, loadedCost)
      47    {
      48        var progress = 100.0 * loadedCost / totalCost;
      49        document.getElementById("bar").style.width = progress + "%";
      50        document.getElementById("message").innerHTML += ("<strong>" + url + "</strong>" + " loaded.<br />");
      51    }
      52}
      哎,似乎完全没有必要对代码进行多余的解释。到目前为止,一个简单的Script加载进度条就完成了,相当的简单。代码可以点击这里下载,也可以点击这里查看效果。

      不过事情到此为止了吗?事实上,我对这个Solution不怎么满意,虽然对于大多数情况应该已经够用了。可以注意到,我将Jeffz.Sample.LoadScripts实现成为了一个Singleton,也就是说,没有另外一个和它一样的实例。并且在load方法的一开始就判断是不是正在加载,如果是,那么会抛出一个异常。实现了这么一种“单线程”的加载,直接原因是受限于Sys.ScriptLoader的实现。

      请看Sys.ScriptLoader代码的第44行,它使用了eval来“邪恶”地进行了script加载完成时的回调。这其实对于开发人员是一种非常难受的实现,因为eval,所以无法地将一个函数的引用作为回调函数来传递。唯一能做的就是只能把“根代码”作为字符串形式来交给Sys.ScriptLoader。虽然还是能够通过Sys.ScriptLoader实现“并发”的Script加载(说白了最多像Sys.ScriptLoader一样建一个队列嘛),但是代码量自然而然就上去了,开发的复杂度也提高了。

      不过我认为,这种“单线程”的script加载已经足够用于大多数情况了。而且如果真的有“特殊”要求,参照Sys.ScriptLoader这个如此清晰明了的范例,自己重新写一个对于广大开发人员来说,难道还不是易如反掌的事情吗?
      http://www.cnblogs.com/JeffreyZhao/archive/2006/09/13/502357.html

    零七网部分新闻及文章转载自互联网,供读者交流和学习,若有涉及作者版权等问题请及时与我们联系,以便更正、删除或按规定办理。感谢所有提供资讯的网站,欢迎各类媒体与零七网进行文章共享合作。

    零七广告
    零七广告
    零七广告
    零七广告