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

邀月的猪

邀月的猪

2016-02-19 17:57

生活已是百般艰难,为何不努力一点。下面图老师就给大家分享Sys.ScriptLoader与JS加载进度条的实现,希望可以让热爱学习的朋友们体会到设计的小小的乐趣。

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

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

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

(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/webkaifa/)

   

    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加载的进度条。不过既然写到了这里,也就继续把它进行一个简单的实现。

(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/webkaifa/)

  首先是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
   5script runat="server"
   6
   7/script
   8
   9html xmlns="http://www.w3.org/1999/xhtml"
  10head runat="server"
  11    titleLoad 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
  30body 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

展开更多 50%)
分享

猜你喜欢

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

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

AJAX 进度条实现代码

Web开发
AJAX 进度条实现代码

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

jsp进度条

Web开发
jsp进度条

IOS实现简单的进度条功能

编程语言 网络编程
IOS实现简单的进度条功能

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

ajax进度条

Web开发
ajax进度条

Flash AS 2.0实例教程:加载进度条之点点进度

flash教程
Flash AS 2.0实例教程:加载进度条之点点进度

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

lbound 方法

lbound 方法

WPS2010 Ctrl+26个字母的作用介绍

WPS2010 Ctrl+26个字母的作用介绍
下拉加载更多内容 ↓