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

js|加载

今天有人问我,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" 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一样建一个队列嘛),但是代码量自然而然就上去了,开发的复杂度也提高了。

  另外,Sys.ScriptLoader在加载某Script出错时也没有提示,而是直接退出,这个也不是很理想。

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

时间: 2025-01-21 07:04:47

Sys.ScriptLoader与JS加载进度条的实现的相关文章

pace.js页面加载进度条插件_javascript技巧

本文简单介绍插件pace.js. 在页面中引入Pace.js,页面就会自动监测你的请求(包括Ajax请求),在事件循环滞后,会在页面记录加载的状态以及进度情况.此插件的兼容性很好,可以兼容IE8以上的所有主流插件,而且其强大之处在于,你还可以引入加载进度条的主题样式,你可以选择任意颜色和多种动画效果(例如简约.闪光灯,MAC OSX,左侧填充,顶部填充,计数器和弹跳等等动画效果),如果你擅长修改css动画,那你就可以做出无限种可能性的动画,为你的网站增添个性化特色! 调用方法: 引入Pace.j

浅析JS异步加载进度条_javascript技巧

展现效果: 1) 当点击Load的时候,模拟执行异步加载. 浏览器被遮挡. 进度条出现. 实现思路: 1.当用户点击load button执行异步请求. 调用方法 出现加载条 2.怎么实现进度条呢? 1) 在document.body 新增一个div.覆盖浏览器. 设置背景会灰色. z-index = 999. 加载的时候让用户无法修改界面值 2) 在document.body 新增一个动态的div. 代码实现: Main.html: <!DOCTYPE html> <html>

jquery插件NProgress.js制作网页加载进度条

  这篇文章主要介绍了jquery插件NProgress.js制作网页加载进度条的相关资料,需要的朋友可以参考下 NProgress.js是极细的纳米级进度条,用现实的细线条动画让用户看到网页正在发生的事情! 你也许已经在 Youtube 上看过了那道红色激光脉冲,它会在你切换页面时出现.其实许多移动浏览器的进度条都是这个样式,但是在网页上实现可不多见.不过,有了 NProgress 这个 jQuery 插件,你也可以轻松实现! NProgress.js应用于复杂网页的细长进度条.由 Googl

js网页加载进度条代码

js网页加载进度条代码 <script LANGUAGE="JAVASCRIPT"> var timerID=null;     <!--延时变量--> var count=0;          <!--表示进度的循环变量--> var running=false;    <!--是否正在进行格式化的标志量--> function RandomNumber(max)        <!--用来产生随机数的函数--> {var

JS实现网页加载进度条实例

网页进度条能够更好的反应当前网页的加载进度情况,loading进度条可用动画的形式从开始0%到100%完成网页加载这一过程.但是目前的浏览器并没有提供页面加载进度方面的接口,也就是说页面还无法准确返回页面实际加载的进度,本文中我们使用jQuery来实现页面加载进度条效果. HTML 首先我们要知道的是,目前没有任何浏览器可以直接获取正在加载对象的大小.所以我们无法通过数据大小来实现0-100%的加载显示过程.因此我们需要通过html代码逐行加载的特性,在整页代码的若干个跳跃行数中设置节点,进行大

二款 js 图片预加载进度条(Mootools)

提供二款图片预加载进度条效果代码,可以对网站图片加载过慢时进行提供,这样可以增加用户体验哦,我们利用了一款js与mootools实例代码. <script> var l=0; var imgs; var sum=0; function chk(){   document.getelementbyid("aa").innertext=""+((sum-l)*100/sum)+"%"   l--;   if (l==0){     for

自己动手制作基于jQuery的Web页面加载进度条插件_jquery

静态效果的实现 网页顶部加载进度条,近年来很流行,很多网站都采用了这种加载方式.网上也有这样类似的插件,今天我们总结一下网页顶部线性页面加载进度条. 大体的写法如下: body{ margin:0; } #progress { position:fixed; height: 2px; background:#2085c5; transition:opacity 500ms linear } #progress.done { opacity:0 } #progress span { positio

用jQuery模拟页面加载进度条的实现代码_jquery

因为我们无法通过任何方法获取整个页面的大小和当前加载了多少,所以想制作一个加载进度条的唯一办法就是模拟.那要怎么模拟呢? 我们知道,页面是从上往下执行的,也就是说我们可以大致估算出在页面的某个位置加载了多少,而后用jq模拟出一个进度条来显示. 首先我们先画一个进度条的样子,也就是上图图中的样子,这个不用过多说明,自己看代码 CSS 复制代码 代码如下: *{margin:0;padding:0;font-size:12px} .loading{position:relative;top:0;le

jquery实现加载进度条提示效果_jquery

本文实例讲述了jquery实现加载进度条提示效果代码.分享给大家供大家参考.具体如下: 运行效果截图如下: 具体代码如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>进度条</title> <script type="text/javascript" src="http://lib.sinaapp.co