• <noscript id="eom2a"><optgroup id="eom2a"></optgroup></noscript>
    <tt id="eom2a"><small id="eom2a"></small></tt>
    <input id="eom2a"></input>
  • <div id="eom2a"><small id="eom2a"></small></div>
    <td id="eom2a"><small id="eom2a"></small></td>
  • 您的位置:知識庫 ? Web前端

    前端基礎框架的思考和嘗試

    作者: 佚名  來源: 博客園  發布時間: 2011-01-03 22:00  閱讀: 1515 次  推薦: 2   原文鏈接   [收藏]  
    摘要:前端基礎框架是一直在思考類似的關于js模塊和文件管理的方式,于是,經歷了好幾天的苦思冥想,稍微做了些嘗試。下面會細細道來前端基礎框架的詳細內容。

      近日我一直在思考類似的關于js模塊和文件管理的方式。正好團隊里也正有這樣的需求,于是,經歷了好幾天的苦思冥想,稍微做了些嘗試。下面會細細道來。

      js模塊和文件的管理

      基于這個title,前提是我們已經明確了我們有了一個組件或者js methods 的lib,我們暫且把它叫做庫,庫里面存儲了很多我們常用的東西,比如js插件,封裝好的methods 
      以及其他的一些lib組件。為了更好的管理我們這些顆粒化的js文件,我們的庫通常都是呈顆粒化的。基于這種情況,我們可以說一個js文件就對應一個模塊module,他有他相對獨立的功能。這種管理模式是目前大多數主流框架的文件和模塊管理模式,如YUI,EXT等,這樣的好處是,可以按需調用。并且調用的模塊一目了然。但是這樣也有一個弊端,就是如果一個頁面需要多個模塊的支持,那么自然就需要加載對應的多個模塊的js文件,http連接數自然會增加。這對網站的性能來說當然是不好的。所以,YUI等成熟的框架自然不會遺漏這個問題,他們也有一套自己注冊和管理模塊的機制(可以參考YUI的register和loader模塊)

      當然,jQuery憑借他易用的api風格和強大的選擇器也贏得了很大的市場,但是我們通常喜歡把jQuery叫做一個方法庫,而不是框架的原因是它相對于其他框架而言的話,對模塊和文件的管理就稍遜一籌。雖然他后來的新版本也提供了自己的模塊管理機制...

      但是,這并不存在誰對誰錯,誰好誰壞的問題,只是各自的側重點不同而已。建站者選擇誰只是看誰更適合自己而已。有些企業覺得YUI的架構模式更適合自己,于是選擇了跟他相似的模式,于是有了百度的Tangram,淘寶的kissy,有的企業覺得jQuery更適合現在的自己,于是選擇的jQuery,比如豆瓣,于是也有了克軍的輕量級前端框架Do。我相信每個團隊能夠出一套自己的框架或者庫都是不容易的,都是需要時間積累的,所以我從不輕易地評論別人的成果。

      主流的思路

      由于不是簡單的把頁面上加載的<script>轉變成動態scriptNode添加,所以需要考慮的問題其實并不少。
      比如我們要加載一個新模塊a,對應的顆粒化文件為a.js,那么我們大概可以表示為

    start loading -- a.js

      這當然是最簡單的一種情況,復雜的情況通常都發生在存在模塊依賴的時候,假如我們引用了jQuery,然后用jq寫了一個插件a,那么我們加載順序就得是:

    start loading jQuery.js --> a.js

      如果模塊依賴進一步復雜,比如:

    1. a : require [b, c] //a 模塊需要b,c模塊
    2. b : require [c, d] //b模塊需要c, d模塊
    3. c : require [f] //c模塊需要f模塊
    4. d : no-require
    5. f : no-require

      那么我們的加載順序應該是怎么樣的呢,就應該是:

    Start loading: f.js --> d.js --> c.js --> b.js --> a.js
    或者:         d.js 
    --> f.js --> c.js --> b.js --> a.js

      有人會想會不會存在循環依賴的問題,比如 a依賴b, b依賴a,這在理論上是不可能的,因為js是順序執行的,如果出現循環依賴的情況的話就永遠不知道誰在前誰在后...
      基于這種思路,我們需要在調用的時候告知程序,a模塊需要b模塊和c模塊,同時b模塊又需要c和d模塊,如果想復雜一點,這種循環的遞歸和排序以及判斷重復加載的問題就很頭疼。 

      另:由于js是單線程的,一個模塊的加載是需要時間的,而且沒有編譯過程,只有在運行到加載函數的時候再去找它所依賴的代碼片段,有可能新模塊還沒加載完全,js仍在繼續執行調用新模塊中的方法,就會報錯。解決的辦法由兩個,一個是創建一個阻塞的環境,讓每次的加載函數的執行時間和加載時間一樣長,不過如果這樣的話其實就和在head里直接引用js文件的方式一樣了。當所需加載模塊過多時,性能永遠是個大問題。
      另外一個方法就是創建一個非阻塞的環境,通過回調函數來執行接下來的代碼,這樣在加載多模塊時對性能有很大提升,就是函數體中的代碼邏輯顯得稍微亂一點。可以看到,YUI就是用的這種方式,另外豆瓣的Do也是這種方式。畢竟,性能為王。

      script的插入方式

      1.動態創建scriptNode,設置其src,插入到head中;

      2.xmlHttp同步載入js,解析請求回來的reponseText,一是存在兼容性問題,還有跨域問題,以及對客戶端網絡高依賴的問題。

      綜上,相比較而言,采用的模式為第一種。 

      目的

      1.讓js模塊和html文檔分離開來,讓程序自動去探尋模塊依賴的層級順序,而不是編碼者。
      2.讓js模塊的調用和管理盡可能的容易,各取所需。
      3.創建非阻塞的loading模式,提高頁面性能。 

      使用
      由于篇幅所限,具體代碼說明我就不細說了,這里放出源碼,有興趣的朋友可以看看:

    1. /**
    2. * Author : hongru.chen
    3. * version : 0.1
    4. * name : Module_v0.1.js
    5. *
    */
    6.
    7. (function () {
    8. if (typeof JS === 'undefined') arguments[0].JS = this.JS = {
    9. scriptQueue:[] //用來加載的隊列
    10. };
    11.
    12. var extend = function (destination, source, override) {
    13. if (!override) override = true;
    14. for (var property in source) {
    15. if (override && !(property in destination)) destination[property] = source[property];
    16. }
    17. return destination;
    18. };
    19. var createScriptNode = function(url) {
    20. var script = document.createElement("script");
    21. script.type = "text/javascript";
    22. script.charset = "utf-8";
    23. script.src = url;
    24. return script;
    25. };
    26. var head = function () {
    27. return document.getElementsByTagName("head")[0]
    28. }();
    29.
    30. //遞歸遍歷依賴模塊,輸出loading隊列
    31. var _queue = function (name) {
    32. !JS._regedModules[name]['inQueue'] && JS.scriptQueue.push(name);
    33. JS._regedModules[name]['inQueue'] = true;
    34.
    35. if (JS._regedModules[name]['require']) {
    36. var reqL = JS._regedModules[name]['require'];
    37. for (var i=0; i < reqL.length; i++) {
    38. if (JS._regedModules[reqL[i]]['inQueue']) return JS.scriptQueue;
    39. JS._regedModules[reqL[i]]['inQueue'] = true;
    40. JS.scriptQueue.push(reqL[i]);
    41.
    42. arguments.callee(reqL[i]);
    43. }
    44. } else {
    45. return JS.scriptQueue;
    46. }
    47. };
    48.
    49. // 添加入模塊隊列
    50. JS.add = function (name, options, r) {
    51. return new JS.add.prototype.register(name, options, r);
    52. }
    53.
    54. JS.add.prototype.register = function (name, options, r) {
    55. if (!JS._regedModules) JS._regedModules = {};
    56. if (JS._regedModules[name]) throw new Error('warning: module "'+name+'" is already added!');
    57. JS._regedModules[name] = {};
    58.
    59. if (typeof options == 'object') {
    60. extend(JS._regedModules[name], options)
    61. }
    62. else {
    63. var arg = arguments;
    64. JS._regedModules[name]['url'] = arg[1];
    65. if (!!arg[2]) JS._regedModules[name]['require'] = arg[2];
    66. if (!!arg[3]) {
    67. for (var i=3; i<arg.length; i++) {
    68. JS._regedModules[name][arg[i]] = arg[i];
    69. }
    70. }
    71. }
    72.
    73. this.add = function (n, p, r) {
    74. return JS.add(n, p, r);
    75. }
    76.
    77. return this;
    78. }
    79.
    80. // 加載模塊
    81. JS.use = function (n, callback, logId) {
    82. var _q = _queue(n) || this.scriptQueue;
    83. this.load(_q, callback, logId);
    84. }
    85.
    86. JS.load = function (arr, callback, logId) {
    87. var i = 0;
    88. if (arr.length === 0) {
    89. !!callback && callback();
    90. return;
    91. }
    92. var curModule = arr.pop();
    93.
    94. if (!!logId && typeof logId == 'string') {
    95. var log = document.createElement('p');
    96. log.innerHTML = 'module "' + curModule + '" is loaded';
    97. document.getElementById(logId).appendChild(log);
    98. }
    99.
    100. __load(curModule, function () {
    101. JS.load(arr, callback, logId);
    102. });
    103.
    104. }
    105.
    106. __load = function (name, cb) {
    107. if (JS._regedModules[name]['isLoaded']) return;
    108. var path = JS._regedModules[name]['url'];
    109. var _s = createScriptNode(path);
    110. head.appendChild(_s);
    111.
    112. _s.onload = _s.onreadystatechange = function () {
    113. if ((!this.readyState) || this.readyState === "loaded" || this.readyState === "complete" ) {
    114. JS._regedModules[name]['isLoaded'] = true;
    115. !!cb && cb();
    116. _s.onload = _s.onreadystatechange = null;
    117. }
    118. }
    119. _s.onerror = function () {
    120. _s.onload = _s.onerror = undefined;
    121. throw new Error (_s.src + ' loaded failed!');
    122. head.removeChild(_s);
    123. }
    124. }
    125. })(window);

      引入了這樣的模塊管理機制后,所有的頁面都只需要加載一個文件,就是上面的Module_v0.1.js;里面提供了一個命名空間JS,提供了兩個主要的方法JS.add和JS.use.你可以這樣使用它:

    1. <html>
    2.
    <head>
    3.
    <script type="text/javascript" src="Module_v.01.js"></script>
    4.
    <script type="text/javascript">
    5. JS.add(...)
    6. JS.use(...)
    7. </script>
    8.
    </head>
    9.
    <body>...</body>
    10.
    </html>

      具體的使用方式可以如下:

    1. JS.add('a', {
    2. url : 'http://www.cnblogs.com/1293183133/js/***.js',
    3. require : ['b','c']
    4. })
    5. JS.add('b', {
    6. url : 'http://www.cnblogs.com/1293183133/js/***.js'
    7. })
    8. JS.add('c', {
    9. url : 'http://www.cnblogs.com/1293183133/js/***.js'
    10. })
    11.
    12. JS.use('a', function () {
    13. //you code
    14. })

      如果你覺得這樣的調用方式太麻煩,可以寫成方法鏈式的調用: add的第一個參數是自定義的模塊名,第二個參數可以是對象{},里面包含,對應模塊的js文件的url,和此模塊的依賴模塊,如果沒有就不寫。
      第二個參數也可以不是對象,比如上面例子里后面幾個add,就直接是第一個為模塊名,第二個為對應模塊的js文件url,第三個參數可選,為所依賴的模塊名,是一個Array.

      use 的第一個參數為調用模塊名,第二個參數為回調函數。 

      關注的幾個問題
      1。模塊會不會重復加載?
    -- 不會,當判斷此模塊之前已加載過,就會直接執行回調。不會重復加載。

      2。add的模塊有重名時怎么處理?
    --如果add的模塊有重名,理論上是不允許,如果發現,會報錯提醒。

      3。add模塊的時候需不需要考慮add的順序?
    --不需要,會自動甄別所依賴的模塊,按依賴優先級載入。如果不放心,還提供了一個log日志功能,監測是否按正確順序載入。只需在use方法調用的時候,寫上第三個參數,如:

    1. <div id="log"></div>
    2. <script>
    3. JS.use('a', function () {
    4. //todo
    5. },'log')
    6. </script>

      第三個參數為所要顯示載入信息的dom元素id。結果會如下顯示:

      4。這樣的方式對性能到底有沒有好處?
      --答案,有的,下面上普通的阻塞和非阻塞方式載入jQuery源碼的對比圖:
      直接頁面上載入js的方式

    <script type="text/javascript" src="http://common.cnblogs.com/script/jquery.js?99"></script>

     

     

      最后一條長長的載入時間就是jq的載入時間,故用戶看到完整頁面的時間為整個文件全部載入完成后的時間,大概為500ms左右(這單單是一個空文檔載入jquery文件的時間)。 

      以非阻塞方式載入時: 

    1. <script type="text/javascript">
    2. JS.add('jq', 'http://common.cnblogs.com/script/jquery.js?111');
    3. JS.use('jq', function () {
    4. alert($)
    5. })
    6. </script>

     

      為了保證每次載入不會從緩存中讀取,我加了個版本號?111;可以發現,上面的藍線為用戶感知到的頁面download時間,在20ms左右,而jq文件的加載在藍線后面,所以說這部分時間是用戶感知不到的。對用戶體驗的提升應該是大有幫助的。就是給服務器增加了并發連接數。

      好的,文章大概到這里,可能有人會說,基本上同樣的事情Do.js,using.js,require.js等小眾型框架已經都有了 ,為什么要自己再寫?恩...別人的東西始終是別人的,自己做過的東西才真正是自己的。

      我當然不敢說比別的大牛們考慮的更周到,效率更高,但是希望能在自己的編碼中得到一點提高吧。

    2
    0
    標簽:Web 框架 前端

    Web前端熱門文章

      Web前端最新文章

        最新新聞

          熱門新聞

            黄色网_免费在线黄色电影_黄色成人快播电影_伦理电影_黄色片