【原创】Web 前端脚本文件响应式异步加载器(框架页兼容)

我之前开源了【JavaScript 文件响应式异步加载器】v0.6,用于在 Web 前端统一管理单页开发中的 JS 模块加载,方便、优雅、可靠。

但若要用框架页的模式来开发较为复杂的 Web 应用,统一管理父子页面的 JS、CSS 又是一件麻烦的事……

框架页是一种 HTML 模块化的基础技术,我们通常希望整个网页在视觉上浑然一体,而非被框架分割成一块又一块,所以不同的框架页通常使用同一套基础的 JS、CSS 脚本库。一般的做法是在每个框架页的 <head /> 中都硬编码上完全相同的基础库引用,虽然浏览器缓存会消除文件的重复下载,但 JS 代码会在每个框架页的上下文中再运行一遍,多消耗 N 倍的 CPU 执行时间、多占用 N 份相同大小的内存……

好在框架页之间有 Window 对象之间的引用,好在 JavaScript 的函数可以用 call/apply 方法轻松改变执行上下文

框架页稳定版 v0.8

//
//                >>>  EasyImport.js  <<<
//
//
//      [Version]    v0.8  (2014-10-31)  Stable
//
//      [Usage]      Only for loading JavaScript files in All Kinds of Web,
//                   with JS/CSS-Inherit support for Frames.
//
//
//              (C)2013-2014    SCU FYclub-RDD
//

(function(BOM, DOM) {
  // ----------- Private Attribute ----------- //
  var UA = navigator.userAgent,
    RootPath,
    JS_List = [],
    CallBack,
    NameSpace = [],
    EIJS = {
      AsyncJS: [],
      DOM_Ready: []
    };
  // ----------- Inner Basic Member ----------- //
  var IE_Ver =
    UA.match(/MSIE (\d+)\.\d/i) || UA.match(/Trident\/.+; rv:(\d+)\.\d/i);
  IE_Ver = IE_Ver ? Number(IE_Ver[1]) : NaN;
  var old_IE = IE_Ver && IE_Ver < 9,
    is_Pad = UA.match(/Tablet|Pad|Book|Android 3/i),
    is_Phone = UA.match(/Phone|Touch|Symbian/i);
  var is_Mobile = is_Pad || is_Phone || UA.match(/Mobile/i);

  function DOM_Loaded_Event(DOM_E, CB_Func) {
    if (!old_IE) DOM_E.addEventListener('load', CB_Func, false);
    else
      DOM_E.attachEvent('onreadystatechange', function() {
        if (DOM_E.readyState.match(/loaded|complete/)) CB_Func.call(DOM_E);
      });
  }
  function DOM_Ready_Event(_CB) {
    if (!old_IE) DOM.addEventListener('DOMContentLoaded', _CB, false);
    else if (self !== top) DOM_Loaded_Event(DOM, _CB);
    else
      (function() {
        try {
          DOM.documentElement.doScroll('left');
          _CB.call(DOM);
        } catch (Err) {
          setTimeout(arguments.callee, 1);
        }
      })();
  }
  function $_TN(HTML_Elements, TagName) {
    return HTML_Elements.getElementsByTagName(TagName);
  }
  function NewTag(TagName, AttrList) {
    var NE = DOM.createElement(TagName);
    if (AttrList) for (var AK in AttrList) NE[AK] = AttrList[AK];
    return NE;
  }
  function PagePath_IE(_BOM) {
    var _PP = _BOM.document.URL;
    _PP = _PP.split('/');
    if (_PP.length > 3) _PP.pop();
    _PP.push('');
    return _PP.join('/');
  }

  var DOM_Head = $_TN(DOM, 'head')[0];
  var DOM_Title = $_TN(DOM_Head, 'title')[0];
  try {
    var _DOM = parent.document;
  } catch (Err) {
    var Inherit_Root = true;
  }
  // ----------- Frame Inherit (JS) ----------- //
  if (Inherit_Root || self === top) {
    //  Default NameSpaces (Offical Support Libraries)
    if (!IE_Ver)
      NameSpace = [
        { jQuery: true },
        {
          $: function() {
            return this.jQuery(
              arguments[0],
              arguments.length > 1 ? arguments[1] : this.document
            );
          }
        }
      ];
  } else {
    var _NS;
    if (!IE_Ver) {
      _NS = parent.ImportJS.NS();
      for (var i = 0; i < _NS.length; i++)
        for (__NS in _NS[i]) {
          var _NS_Shell = _NS[i][__NS];
          if (_NS_Shell === true) {
            var _NS_ = parent[__NS];
            self[__NS] = _NS_;
          } else if (typeof _NS_Shell == 'function')
            self[__NS] = (function(_NS_S) {
              return function() {
                return _NS_S.apply(self, arguments);
              };
            })(_NS_Shell);
        }
    } else {
      _NS = [];
      var _SE = $_TN($_TN(_DOM, 'head')[0], 'script');
      for (var i = 2, JS_URL; i < _SE.length; i++) {
        JS_URL = _SE[i].src;
        if (JS_URL == '' || JS_URL.match(/EasyImport.*\.js/i)) continue;
        if (IE_Ver < 8) JS_URL = PagePath_IE(parent) + JS_URL;
        _NS.push(JS_URL);
      }
      BOM.onbeforeunload = function() {
        var SE = $_TN(DOM, 'script');
        for (var i = 0; i < SE.length; i++) SE[i].parentNode.removeChild(SE[i]);
        SE = null;
        DOM.write('');
        DOM.clear();
        BOM.onbeforeunload = null;
        BOM.close();
        top.CollectGarbage();
      };
    }
    NameSpace = _NS;
  }
  // ----------- Frame Inherit (CSS) ----------- //
  if (!Inherit_Root && self !== top) {
    var _LE = _DOM.styleSheets;
    for (var i = 0, CSS_URL; i < _LE.length; i++) {
      if (_LE[i].rel.indexOf('nofollow') == -1) continue;
      CSS_URL = _LE[i].href;
      if (IE_Ver < 8) CSS_URL = PagePath_IE(parent) + CSS_URL;
      DOM_Head.appendChild(
        _LE[i][old_IE ? 'owningElement' : 'ownerNode'].cloneNode(true)
      ).href = CSS_URL;
    }
  }
  // ----------- Standard Mode Meta Patches ----------- //
  if (is_Mobile) {
    if (!old_IE) {
      var is_WeChat = UA.match(/MicroMessenger/i),
        is_UCWeb = UA.match(/UCBrowser|UCWeb/i);
      DOM_Head.insertBefore(
        NewTag('meta', {
          name: 'viewport',
          content: [
            ['width', is_WeChat || is_UCWeb ? 320 : 'device-width'].join('='),
            'initial-scale=1.0',
            'target-densitydpi=medium-dpi'
          ].join(',')
        }),
        DOM_Title
      );
    } else
      DOM_Head.insertBefore(
        NewTag('meta', {
          name: 'MobileOptimized',
          content: 320
        }),
        DOM_Title
      );
    DOM_Head.insertBefore(
      NewTag('meta', {
        name: 'HandheldFriendly',
        content: 'true'
      }),
      DOM_Title
    );
  }
  if (IE_Ver)
    DOM_Head.insertBefore(
      NewTag('meta', {
        'http-equiv': 'X-UA-Compatible',
        content: 'IE=Edge, Chrome=1'
      }),
      DOM_Title
    );
  // ----------- Inner Logic Module ----------- //
  function xImport(JS_URL, CB_Func) {
    var SE = DOM.createElement('script');
    with (SE) {
      type = 'text/javascript';
      charset = 'UTF-8';
      src = JS_URL;
    }
    if (CB_Func) DOM_Loaded_Event(SE, CB_Func);
    return DOM_Head.appendChild(SE);
  }
  function LI_Add(LQ, RP, FN) {
    if (!FN.match(/^http(s)?:\/\//)) FN = RP + FN;
    LQ.push({ URL: FN });
  }
  function SL_Set(RP, List0) {
    for (var i = 0; i < List0.length; i++)
      if (!(List0[i] instanceof Array)) List0[i] = [List0[i]];
    var List1 = [];
    for (var i = 0, k = 0; i < List0.length; i++) {
      List1[k] = [];
      for (var j = 0, _URL; j < List0[i].length; j++) {
        if (typeof List0[i][j] == 'string') LI_Add(List1[k], RP, List0[i][j]);
        else {
          var _Rule = {
              old_PC: old_IE,
              Mobile: is_Mobile,
              Phone: is_Phone,
              Pad: is_Pad
            },
            no_Break = true;
          for (RI in _Rule)
            if (_Rule[RI]) {
              if (List0[i][j][RI] === false) no_Break = false;
              else if (List0[i][j][RI]) LI_Add(List1[k], RP, List0[i][j][RI]);
              break;
            }
        }
        if (no_Break && !List1[k][j] && List0[i][j].new_PC)
          LI_Add(List1[k], RP, List0[i][j].new_PC);
      }
      if (List1[k].length) k++;
    }
    return List1;
  }
  function CB_Set(List, Index) {
    var Item = List[Index + 1];
    if (Item[0].CallBack) {
      var AJSQ = EIJS.AsyncJS;
      var AJS_NO = AJSQ.push(0) - 1;
      var AJS_CB = function() {
        if (++AJSQ[AJS_NO] == Item.length) Item[0].CallBack();
      };
    }
    return function TF_Import() {
      for (var n = 0; n < Item.length; n++) xImport(Item[n].URL, AJS_CB);
    };
  }
  function CB_Chain(JS_RC, Final_CB) {
    var DRQ = EIJS.DOM_Ready;
    var DRQ_NO = DRQ.push(0) - 1;
    for (var k = JS_RC.length - 2, l = 0; k > -2; k--, l++) {
      if (!l) {
        if (Final_CB) {
          JS_RC[k + 1][0]['CallBack'] = function(iEvent) {
            if (++DRQ[DRQ_NO] == 2) Final_CB.apply(self);
          };
          DOM_Ready_Event(JS_RC[k + 1][0].CallBack);
        } else if (k > -1) {
          var Last_Script = JS_RC[k + 1][0].URL;
          JS_RC[k][0]['CallBack'] = function(iEvent) {
            if (++DRQ[DRQ_NO] == 2) xImport(Last_Script);
          };
          DOM_Ready_Event(JS_RC[k][0].CallBack);
          continue;
        }
      }
      if (k > -1) JS_RC[k][0]['CallBack'] = CB_Set(JS_RC, k);
    }
  }

  // ----------- Open API ----------- //
  BOM.ImportJS = function() {
    var Func_Args = Array.prototype.slice.call(arguments, 0);

    if (typeof Func_Args[0] == 'string') RootPath = Func_Args.shift();
    else RootPath = './';
    if (Func_Args[0] instanceof Array) JS_List = Func_Args.shift();
    else throw "Format of Importing List isn't currect !";
    if (typeof Func_Args[0] == 'function') CallBack = Func_Args.shift();
    else CallBack = null;
    if (Func_Args[0] instanceof Array && !IE_Ver)
      NameSpace = NameSpace.concat(Func_Args[0]);
    if (IE_Ver) {
      JS_List = NameSpace.concat(JS_List);
      NameSpace = [];
    }

    var JS_Item = SL_Set(RootPath, JS_List);
    CB_Chain(JS_Item, CallBack);
    xImport(JS_Item[0][0].URL, JS_Item[0][0].CallBack);
  };

  BOM.ImportJS.UA = {
    IE: !!old_IE,
    Modern: !old_IE,
    Mobile: !!is_Mobile,
    Pad: !!is_Pad,
    Phone: !!is_Phone
  };
  BOM.ImportJS.NS = function() {
    return NameSpace;
  };
})(self, self.document);

上一篇
【笔记】Web 前端开发疑难 【笔记】Web 前端开发疑难
网页布局 方方面面疑难解答的优秀文章,由本人肉眼搜集于 sogou.com(按一般的技术学习路线排序;技术更迭,未完待续)—— 【CSS 盒模型】元素长宽计算与 padding、border 的关系 http://bbs.aau.cn/fo
2014-10-29
下一篇
【原创】JavaScript 文件响应式异步加载器(单页稳定版) 【原创】JavaScript 文件响应式异步加载器(单页稳定版)
我在做《i 飞扬》电子杂志 HTML5 在线版的过程中,为了在不改变 Web 前端程序猿的编程思维习惯的前提下,保证整个 WebApp 的好用、可靠,自己开发了一个【JavaScript 文件响应式异步加载器】—— EasyImport.j
2014-10-09