分享
 
 
 

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

王朝html/css/js·作者佚名  2008-05-30
窄屏简体版  字體: |||超大  

今天有人问我,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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有