(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的属性报错时,才会发现问题 */