【原创】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.js
2014-10-09