今天有人問我,163郵箱那樣的javascript加載進(jìn)度條是如何實(shí)現(xiàn)的。
我不知道,不過實(shí)現(xiàn)一個(gè)不難,因?yàn)?lt;script />有onload和onreadystatechange。還有就是,我們有Atlas。
Atlas中有個(gè)類:Sys.ScriptLoader,它的作用就是在頁面中依次地加載多個(gè)Script文件。在實(shí)現(xiàn)之前,先來分析一下這個(gè)類的代碼。
1Sys.ScriptLoader = function() {
2
3 // 所有Script的reference對象數(shù)組。
4 var _references;
5 // 所有Script加載完之后執(zhí)行的回調(diào)函數(shù)。
6 var _completionCallback;
7 // 執(zhí)行回調(diào)函數(shù)時(shí)提供的上下文(參數(shù))。
8 var _callbackContext;
9
10 // 當(dāng)前正在加載的Script的HTTP Element(<script />)。
11 var _currentLoadingReference;
12 // 當(dāng)前的Script加載完成后所調(diào)用的回調(diào)函數(shù)。
13 var _currentOnScriptLoad;
14
15 // ScriptLoader唯一的方法,傳入三個(gè)參數(shù),參數(shù)含義不再贅述。
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 // 如果當(dāng)前正在加載某個(gè)Script。
27 // 這表示此方法不是第一次被調(diào)用,而是在某個(gè)Script被加載
28 // 完成后才被調(diào)用,用以加載下一個(gè)Script。
29 if (_currentLoadingReference) {
30 // 查看當(dāng)前Script元素的readyState,IE下為complete,
31 // 其他瀏覽器如FF則為loaded(FF其實(shí)并無此屬性,
32 // 但是下面的代碼會將其設(shè)為loaded)。
33 // 如果加載失敗,則退出。
34 if ((_currentLoadingReference.readyState != 'loaded') &&
35 (_currentLoadingReference.readyState != 'complete')) {
36 return;
37 }
38 else {
39 // 進(jìn)入此分支,表明加載成功。
40
41 // 如果當(dāng)前Script定義了onLoad函數(shù)。
42 if (_currentOnScriptLoad) {
43 // 通過eval調(diào)用(這里是個(gè)麻煩的地方)。
44 eval(_currentOnScriptLoad);
45 // 設(shè)為null,釋放資源。
46 _currentOnScriptLoad = null;
47 }
48
49 // 將相關(guān)事件設(shè)為null以確保釋放資源。
50 if (Sys.Runtime.get_hostType() != Sys.HostType.InternetExplorer) {
51 // 如果當(dāng)前瀏覽器不是IE,見下面的代碼
52 // 會發(fā)現(xiàn)為<script />定義了onload事件。
53 _currentLoadingReference.onload = null;
54 }
55 else {
56 // 如果是IE,見下面代碼會發(fā)現(xiàn)為了
57 // <script />定義了onreadystatechange事件。
58 _currentLoadingReference.onreadystatechange = null;
59 }
60
61 // 最終釋放當(dāng)前的<script />引用。
62 _currentLoadingReference = null;
63 }
64 }
65
66 // 如果還有沒有加載的Script。
67 if (_references.length) {
68 // 出隊(duì)列。
69 var reference = _references.dequeue();
70 // 創(chuàng)建<script />
71 var scriptElement = document.createElement('script');
72 // 設(shè)當(dāng)前的<script />和當(dāng)前加載成功的回調(diào)函數(shù)。
73 _currentLoadingReference = scriptElement;
74 _currentOnScriptLoad = reference.onscriptload;
75
76 if (Sys.Runtime.get_hostType() != Sys.HostType.InternetExplorer) {
77 // 如果不是IE的話,那么為<script />設(shè)屬性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 // 如果執(zhí)行到這里,說明所有的Script已經(jīng)加載完了。
97 // 如果定義了所有Script加載完之后執(zhí)行的回調(diào)函數(shù),
98 // 那么執(zhí)行并釋放資源。
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 />元素。事實(shí)上,它在Atlas中被使用的非常少。
事實(shí)上,Sys.ScriptLoader的代碼非常簡單,我添加的注釋越看越像畫蛇添足。值得注意的是所有的資源都被盡可能的釋放。尤其注意從第99行開始的代碼,if體內(nèi)首先用臨時(shí)變量保留兩個(gè)全局變量,然后再將全局變量釋放。其目的就是避免在completionCallback在執(zhí)行時(shí)拋出異常而導(dǎo)致的內(nèi)存泄露,即使只有萬分之一的可能性。Javascript越多,則越容易造成內(nèi)存泄露,在編寫JS代碼時(shí)最好注意這方面的問題。
接著解釋一下load方法的第一個(gè)參數(shù)references,原本以為這一個(gè)Sys.Reference類的數(shù)組,結(jié)果發(fā)現(xiàn)其實(shí)相差甚遠(yuǎn)。不管怎么樣順便看一下該類的代碼。
1Sys.Reference = function() { 到這里,我想大家也應(yīng)該想到了如何使用Sys.ScriptLoader輕而易舉地制作JS加載的進(jìn)度條。不過既然寫到了這里,也就繼續(xù)把它進(jìn)行一個(gè)簡單的實(shí)現(xiàn)。 首先是aspx文件。 1<%@ Page Language="C#" %> 1var scripts = 1Type.registerNamespace('Jeffz.Sample'); 不過事情到此為止了嗎?事實(shí)上,我對這個(gè)Solution不怎么滿意,雖然對于大多數(shù)情況應(yīng)該已經(jīng)夠用了。可以注意到,我將Jeffz.Sample.LoadScripts實(shí)現(xiàn)成為了一個(gè)Singleton,也就是說,沒有另外一個(gè)和它一樣的實(shí)例。并且在load方法的一開始就判斷是不是正在加載,如果是,那么會拋出一個(gè)異常。實(shí)現(xiàn)了這么一種“單線程”的加載,直接原因是受限于Sys.ScriptLoader的實(shí)現(xiàn)。 請看Sys.ScriptLoader代碼的第44行,它使用了eval來“邪惡”地進(jìn)行了script加載完成時(shí)的回調(diào)。這其實(shí)對于開發(fā)人員是一種非常難受的實(shí)現(xiàn),因?yàn)閑val,所以無法地將一個(gè)函數(shù)的引用作為回調(diào)函數(shù)來傳遞。唯一能做的就是只能把“根代碼”作為字符串形式來交給Sys.ScriptLoader。雖然還是能夠通過Sys.ScriptLoader實(shí)現(xiàn)“并發(fā)”的Script加載(說白了最多像Sys.ScriptLoader一樣建一個(gè)隊(duì)列嘛),但是代碼量自然而然就上去了,開發(fā)的復(fù)雜度也提高了。 不過我認(rèn)為,這種“單線程”的script加載已經(jīng)足夠用于大多數(shù)情況了。而且如果真的有“特殊”要求,參照Sys.ScriptLoader這個(gè)如此清晰明了的范例,自己重新寫一個(gè)對于廣大開發(fā)人員來說,難道還不是易如反掌的事情嗎?
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.add28 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);
關(guān)心一下Sys.ScriptLoader類的代碼可知,reference數(shù)組的每個(gè)元素其實(shí)只是簡單的“{ url : "
2
3<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " 4
5<script runat="server">
6
7</script>
8
9<html xmlns="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>
非常的簡單。使用兩個(gè)DIV制作了一個(gè)最簡單的進(jìn)度條。在點(diǎn)擊按鈕時(shí)調(diào)用了Load()函數(shù)。該函數(shù)隨機(jī)生成了Script鏈接并生成了一個(gè)8元素的scripts數(shù)組。scripts數(shù)組的格式如下:
2[
3 { url : "4 { url : "5 { url : "6];
每個(gè)元素的url屬性不必說,而cost的功能就是表示加載該文件所消耗的時(shí)間的值。這個(gè)值沒有單位,用到的只是這個(gè)值在總共消耗里的比例。另外,可以看到有一個(gè)Script.ashx,其作用是模擬一個(gè)長時(shí)間script加載,它會根據(jù)querystring中的sleep的值將線程休眠一段時(shí)間(至于后面的t,目的只是通過改變querystring來避免點(diǎn)擊按鈕時(shí)瀏覽器的緩存),這個(gè)文件幾乎沒有代碼,可以在范例下載中看到它的實(shí)現(xiàn)。最后通過調(diào)用Jeffz.Sample.LoadScripts.load方法進(jìn)行加載,這就涉及到了下面的代碼,LoadScripts.js:
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}
哎,似乎完全沒有必要對代碼進(jìn)行多余的解釋。到目前為止,一個(gè)簡單的Script加載進(jìn)度條就完成了,相當(dāng)?shù)暮唵巍4a可以點(diǎn)擊這里下載,也可以點(diǎn)擊這里查看效果。
http://m.survivalescaperooms.com/JeffreyZhao/archive/2006/09/13/502357.html
新聞熱點(diǎn)
疑難解答
圖片精選