(function(WIN, DOC, UNDEFINED) {
    //
    var toString = Object.prototype.toString,
        HEAD = "head",
        UTF8 = "utf-8",
        EMPTY = "",
        SPACE = " ",
        STRING = "string",
        OBJECT = "object",
        KEY = "key",
        DEPS = "deps",
        ADDCALLBACK = "addCallback",
        STATE = "state",
        EXPORTS = "exports",
        UNDEFINED_CHARS = "undefined";
    //
    var scriptTimeoutDelay = 5000,
        checkScriptTimeout = true, //
        scriptCharset = UTF8;
    //
    var head = document.getElementsByTagName(HEAD)[0];
    //组件属性
    var _t = WIN.t,
        t = {
            data: {
                callbackHash: {
                    key: "",
                    deps: [],
                    addCallback: null,
                    state: 0, //0表示未完成加载;1表示加载执行完成;
                    exports: {}
                },
                //chrome用来标记当前add的script,浏览器会自动维护确保顺序准确性配套use==>add==>onload
                currentMod: {
                    key: "",
                    deps: [],
                    addCallback: null
                }
            },
            guid: 1
        };
    //设置运行参数
    t.setConfig = function(config) {
        //set config
        if (config) {
            checkScriptTimeout = config.checkScriptTimeout || true;
            scriptTimeoutDelay = config.scriptTimeoutDelay || 5000;
            scriptCharset = config.scriptCharset || UTF8;
        }
    }
    //异常、错误处理
    t.error = function() {
        var args = [].slice.call(arguments),
            e = args.pop();
        if (typeof e == "object") {
            (typeof log != UNDEFINED_CHARS) && log(args.join(SPACE));
            throw e;
        } else {
            args.push(e);
            (typeof log != UNDEFINED_CHARS) && log(args.join(SPACE));
        }
    }
    /** use
    * uris {string|Array} 请求的路径(相对、绝对路径均可)只要保证请求到数据
    * fn(exports) {function} 请求回调
    * everLoadFlag {boolean} 是否请求过了,还一定保持请求
    */
    t.use = function(uris, fn, everLoadFlag) {
        if (!uris) return fn();
        var _exports = []; //_uris = uris.toString().split(','); 
        //array
        if (isArray(uris)) {
            _exports = [];
            //uris必须全部正确加载才能执行fn
            _useByOrder(uris);
            return;
        }
        //string
        if (typeof uris == STRING && !everLoadFlag && (_exports = _isUriLoaded(t.data.callbackHash, uris))) {
            fn(_exports);
            return;
        } else {
            insertScript(uris, fn);
        }
        //是否是加载完成过的uri
        function _isUriLoaded(_callbackHash, uri) {
            var _callbackHash = t.data.callbackHash,
                _mod = {},
                _apUri = rp2apUri(uri);
            if (_callbackHash && (_mod = _callbackHash[_apUri]) && _mod[STATE] && _mod[STATE] == 1) {
                return _mod[EXPORTS];
            }
            return null;
        }
        //按顺序执行数组uris
        function _useByOrder(inneruris) {
            var inneruri = inneruris.shift(),
                _innerExport;
            if (!everLoadFlag && (_innerExport = _isUriLoaded(t.data.callbackHash, inneruri))) {
                _useByOrderNext(_innerExport, inneruris);
            } else {
                insertScript(inneruri, function(_inner) {
                    _useByOrderNext(_inner, inneruris);
                });
            }
        }
        //_useByOrder内部递归重用函数
        function _useByOrderNext(_inner, inneruris) {
            _exports.push(_inner);
            if (inneruris.length > 0) {
                _useByOrder(inneruris);
            } else {
                //uris必须全部正确加载才能执行fn
                return fn.apply(null, _exports); //end
            }
        }
    };
    /** seajs/define
    * http://groups.google.com/group/seajs/browse_thread/thread/92cf62aeae1594da
    * http://www.nodejser.com/?p=146344
    * ie中js文件代码不一定在onload之前执行完成
    * key {string} 自定义模块的键值
    * deps{Array} 依赖项key键值数组,认定必须为数组
    * fn(require,exports,module)  {function} 添加模块的属性构造函数 
    * key暂时保持无效不使用?
    */
    t.add = function(key, deps, fn) {
        var args = [].slice.call(arguments, 0),
            len = args.length,
            _key, _deps, _fn, _argi, _src,
            _add = function() {
                var current = getCurrentScript();
                //需要处理内部的依赖关系?
                if (current) {
                    _src = getScriptAbsoluteSrc(current);
                    t.data.currentMod[KEY] = _src;
                    t.data.callbackHash[_src][ADDCALLBACK] = _fn;
                } else {
                    //insert之前就存了key
                    t.data.currentMod[ADDCALLBACK] = _fn;
                }
            };
        //参数对照
        for (var i = 0; i < len; i++) {
            _argi = args[i];
            if (typeof _argi == STRING) {
                _key = _argi;
            } else if (isArray(_argi)) {
                _deps = _argi;
            } else if (typeof _argi == "function") {
                _fn = _argi;
            }
        }
        var current = getCurrentScript();
        //需要处理内部的依赖关系 deps
        //内部去请求的时候回调函数Onload后面会继续执行下去,,所以addcallback为空,应该是把回调函数放进依赖的执行项
        //加载了js文件并不会立即执行_deps还是要在回调里最后去执行,判断依赖项
        if (current) {
            //ie
            _src = getScriptAbsoluteSrc(current);
            t.data.currentMod[KEY] = _src; //?对ie设置t.data.currentMod是否多余
            t.data.currentMod[DEPS] = _deps;
            t.data.callbackHash[_src][DEPS] = _deps;
            t.data.callbackHash[_src][ADDCALLBACK] = _fn;
        } else {
            //chrome 取消使用insert之前就存了的key
            t.data.currentMod[DEPS] = _deps;
            t.data.currentMod[ADDCALLBACK] = _fn;
        }
    };
    /**  动态创建script
    * http: //www.cnblogs.com/zhujl/archive/2011/12/25/2283550.html
    */
    function insertScript(uri, useCallback, timeoutCallback) {
        var _script = document.createElement("script");
        _script.type = "text/javascript";
        //添加与否创建script标签请求都是异步的
        _script.async = true;
        _script.charset = scriptCharset;
        //ie在创建scrip结点并给src赋值时就开始有http下载了,这与其它浏览器完全不同(其它浏览器是要把script结点加到DOM中才会有http下载的)
        _script.src = uri;

        var timeoutstate = 1; //0,已经获取数据或执行了onload;1,初始状态;不影响执行下面步骤;2,超时状态,不再执行下面步骤;

        //插入script之前
        var _currentScriptSrc = getScriptAbsoluteSrc(_script);
        //赋入当前url的key
        t.data.callbackHash[_currentScriptSrc] = {
            key: _currentScriptSrc,
            deps: [],
            addCallback: null,
            exports: {}, //add中exports返回到回调函数参数中的值
            state: 0//0表示未完成加载
        };
        var _onloadwrapCallback = function() {
            onloadCallback(_currentScriptSrc, useCallback);
        };
        //判断是否已经执行了自定义的超时操作,如果已经执行,即使获取了数据也认定为超时,不做进一步数据处理
        var isTimeout = function() {
            if (timeoutstate == 2) return true;
            timeoutstate = 0;
            return false;
        };
        _script.onerror = function(e) { t.error("script", _currentScriptSrc, "加载失败"); };
        //可以确保src中js执行完成了才出发onload/onreadystatechange=>readyState=loaded
        if (hasPropertyOnload(_script)) {
            _script.onload = function(e) {
                if (isTimeout()) return;
                run(_script, _currentScriptSrc, _onloadwrapCallback);
            };
        } else {
            //ie6注册事件最好使用attachEvent,避免跨页内存泄露
            _script.attachEvent("onreadystatechange", function(e) {
                //opera的script也存在readyState,但如果请求地址不存在,是不会进入onload回调的
                //http://www.cnblogs.com/_franky/archive/2010/06/20/1761370.html#1875070
                //http://www.cn-cuckoo.com/2007/07/16/the-details-for-five-states-of-readystate-9.html(Ajax中的这5中状态)
                if (/(msie)/i.test(navigator.userAgent) && /loaded|complete/i.test(_script.readyState)) {
                    if (isTimeout()) return;
                    //(typeof log != UNDEFINED_CHARS) && log(_currentScriptSrc + "----" + timeoutstate + "--" + _script.readyState);
                    //ie会执行2次
                    //本地js文件不存在时,也会进入本分支,readystate为loaded,网络上不存在的文件也是loaded进入,但超时时间很长
                    run(_script, _currentScriptSrc, _onloadwrapCallback);
                }
            });
        }
        //使用insertBefore防止后面js依赖项需要方法在后面加载       
        // For some cache cases in IE 6-9, the script executes IMMEDIATELY after//?seajs
        // the end of the insertBefore execution, so use `currentlyAddingScript`
        // to hold current node, for deriving url in `define`.
        var currentlyAddingScript = _script;
        head.insertBefore(_script, head.firstChild);
        currentlyAddingScript = null;
        /**超时判断?
        * 立即返回无此数据;可能服务端无返回等待超时;可能有数据但是返回时间超长;
        */
        //添加认证确保可以依靠 超时判断 进行自动脚本处理
        if (checkScriptTimeout) {
            setTimeout(function() {
                if (timeoutstate) {
                    t.error(_currentScriptSrc + "; check by timeoutstate => timeout");
                    removeScript(_script);
                    _onloadwrapCallback = null;
                    timeoutstate = 2;
                    //外部回调函数,主动判断超时后触发
                    timeoutCallback && timeoutCallback();
                }
            }, scriptTimeoutDelay);
        }
    }
    //script加载后
    function run(_script, url, _onloadwrapCallback) {
        _script.onload = _script.onreadystatechange = _script.onerror = null;
        removeScript(_script);
        //chrome
        if (t.data.currentMod[ADDCALLBACK]) {
            //[url]对象在insert之前就定义了,chrome中t.data.currentMod.key暂时固定为空
            t.data.callbackHash[url][KEY] = url;
            t.data.callbackHash[url][DEPS] = t.data.currentMod[DEPS];
            t.data.callbackHash[url][ADDCALLBACK] = t.data.currentMod[ADDCALLBACK];
            //reset
            t.data.currentMod = { key: EMPTY, deps: [], addCallback: null };
        }
        _onloadwrapCallback();
    }
    //构建onload后的回调fn "关键函数" 传入当前要执行的uri
    function onloadCallback(uri, _useCallback) {
        try {
            //inner可能混乱对象
            var inner = {},
                _deps = t.data.callbackHash[uri][DEPS],
                _completeLoad = function() {
                    var _tDataCallbackHash = t.data.callbackHash,
                        _tDataCbAddCb = _tDataCallbackHash[uri][ADDCALLBACK];
                    //t.add页面不一定有t.add方法也能工作?
                    _tDataCbAddCb && _tDataCbAddCb("require", inner);
                    _useCallback && _useCallback(inner);
                    _tDataCallbackHash[uri][STATE] = 1; //1表示已经加载完成
                    _tDataCallbackHash[uri][EXPORTS] = inner;
                },
                _depsFiltered = [],
                innerUri = EMPTY;
            /** 依赖项 
            * 处理add中的请求依赖项
            * 过滤获得未加载完成状态的_deps。。。。需要绝对路径的_deps...
            * 依赖项加载时,只加载没有加载过的key 
            */
            if (_deps && _deps.length) {
                for (var i = 0, len = _deps.length; i < len; i++) {
                    innerUri = rp2apUri(_deps[i]);
                    if (!t.data.callbackHash[innerUri] || t.data.callbackHash[innerUri][STATE] != 1) {
                        _depsFiltered.push(innerUri);
                    }
                }
            }
            //执行之前要先判断依赖项是否加载完成,不重复加载
            if (_depsFiltered.length > 0) {
                t.use(_depsFiltered, function() {
                    _completeLoad();
                });
            } else {
                _completeLoad();
            }
        } catch (e) {
            t.error("script", uri, "加载执行失败", e.name, e.message, e);
        }
    }
    //###公共方法
    function isArray(arr) {
        return toString.call(arr) == "[object Array]";
    }
    //移除script节点
    function removeScript(node) {
        node.parentNode.removeChild(node);
        node = null;
        window.CollectGarbage && CollectGarbage();
    }
    //script是否具有onload属性
    function hasPropertyOnload(script) {
        script = script || document.createElement('script');
        if ('onload' in script) return true;
        script.setAttribute('onload', EMPTY);
        return typeof script.onload == 'function'; // ff true ie false .
    }

    var currentlyAddingScript;
    var interactiveScript;
    //获取当前正在执行代码=>t.add的外部加载script
    function getCurrentScript() {
        if (currentlyAddingScript) {
            return currentlyAddingScript;
        }
        // For IE6-9 browsers, the script onload event may not fire right
        // after the the script is evaluated. Kris Zyp found that it
        // could query the script nodes and the one that is in "interactive"
        // mode indicates the current script.
        // Ref: http://goo.gl/JHfFW
        if (interactiveScript &&
        interactiveScript.readyState === 'interactive') {
            return interactiveScript;
        }
        var scripts = document.getElementsByTagName('script');
        for (var i = 0; i < scripts.length; i++) {
            var script = scripts[i];
            if (script.readyState === 'interactive') {
                interactiveScript = script;
                return script;
            }
        }
    };
    //获取script节点的src绝对路径
    function getScriptAbsoluteSrc(node) {
        return node.hasAttribute ? // non-IE6/7
        node.src :
        // see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
        node.getAttribute('src', 4);
    };
   
    function rp2apUri(rpath) {
        //绝对路径直接返回
        if (/^http:[\/]*/.test(rpath)) {
            return rpath;
        }
        var apath = window.location.href,
            apaths = apath.split('\/'), //aps.pop();
            apathsLen = apaths.length,
            rpaths = rpath.split('/'),
            rpathsLen;
        (rpaths[0] == '.') && rpaths.shift();
        rpathsLen = rpaths.length;
        for (var i = 1; rpathsLen--; i++) {
            if (rpaths[rpathsLen] != '..') {
                apaths[apathsLen - i] = rpaths[rpathsLen];
            }
        }
        return apaths.join("/");
    }
    WIN.t = t;
})(this, this.document, undefined);
/*
add(fn)=>fn的第一个参数require未添加效果
超时仅作为调试参考及显示,利用超时来做可能的处理暂未进行
本地的错误uri立即onload...目前只能在使用exports的属性报错时,才会发现问题
*/

 

_

 
www.LcKey.com 提供支持 
posted on 2012-05-17 05:34  LcKey  阅读(660)  评论(1编辑  收藏  举报