1. /** 
  2.  * SPORE core v1.1 
  3.  * @fileoverview 实现JS命名空间的管理,模块的定义,与自动加载依赖模块的脚本文件。 
  4.  * @author Esoul | [email protected] 
  5.  */ 
  6. (function(){ 
  7.      
  8.     var undefined,          //防止全局污染 
  9.         that = {},          //当前命名空间 
  10.         jsParent = null,    //用于插入script节点的父节点。 
  11.         doc = document,     //变量引用速度更快 
  12.         JSManager = {},     //要暴露出的JS管理器 
  13.         RequireCache = [],  //模块需求列表缓存 
  14.         ExecuteQueue = [],  //模块执行队列 
  15.         RequireList = [],   //总模块需求列表 
  16.         ModuleCache = {},   //模块缓存 
  17.         JSRequested = {},   //已请求过的JS模块 
  18.         RequireTree = {},   //模块需求多叉树 
  19.         Traversed = {},     //遍历缓存 
  20.         ModuleLoaded = [],  //已经加载的模块的名字列表(用于调试) 
  21.         RequireHash = {};   //模块需求列表的位置索引 
  22.  
  23.     /** 
  24.      * @method each 遍历一个对象 
  25.      * @param {Object} obj 可遍历的对象 
  26.      * @param {Function} fn 要操作的函数 
  27.      * @param {Object} bind 遍历函数要绑定的对象 
  28.      */ 
  29.     function $each(obj, fn, bind){ 
  30.         for(var key in obj){ 
  31.             if(obj.hasOwnProperty(key)){ 
  32.                 fn.call(bind, obj[key], key, obj); 
  33.             } 
  34.         } 
  35.     } 
  36.  
  37.     /** 
  38.      * @method loadjs 加载一个JS文件 
  39.      * @param {String} strsrc JS文件的地址 
  40.      */ 
  41.     function loadjs(strsrc){ 
  42.         var js = doc.createElement('script'); 
  43.         js.type = 'text/javascript'
  44.         js.charset = JSManager.$charset; 
  45.         js.src = strsrc; 
  46.         if(!jsParent){ jsParent = doc.getElementsByTagName('head')[0]; } 
  47.         jsParent.appendChild(js); 
  48.     } 
  49.  
  50.     /** 
  51.      * @method loadModule 加载一个模块 
  52.      * @param {String} mName 模块的名称 
  53.      */ 
  54.     function loadModule(mName){ 
  55.         loadjs( (JSManager.$basepath || '') + mName.split('.').join('/') + '.js?version=' + JSManager.$version ); 
  56.     } 
  57.  
  58.     /** 
  59.      * @method setModuleCache 设置模块加载成功 
  60.      * @param {String} mName 模块的名称 
  61.      */ 
  62.     function setModuleReady(mName){ 
  63.         if(ModuleCache[mName] === 'ready'){return;} 
  64.         var nsParent, ns = that, 
  65.             arrName = (mName || '').split('.'); 
  66.         $each(arrName, function(name){ 
  67.             nsParent = ns; 
  68.             ns = ns[name] = ns[name] || {}; 
  69.         }); 
  70.         nsParent[arrName.pop()] = ModuleCache[mName].call(null,that) || {}; 
  71.         ModuleCache[mName] = 'ready'
  72.         ModuleLoaded.push(mName); 
  73.     } 
  74.  
  75.     /** 
  76.      * @method traverse 深度遍历模块引用多叉树,设置模块加载成功 
  77.      * @param {String} name 节点名称 
  78.      */ 
  79.     function traverse(name){ 
  80.         if(!name || Traversed[name]){return;}   //确保每个节点只能访问一次(避免循环引用造成的死锁) 
  81.         Traversed[name] = true;                 //事实上一个初始化模块,必定只允许运行一次 
  82.         $each(RequireTree[name],traverse); 
  83.         setModuleReady(name); 
  84.     } 
  85.  
  86.     /** 
  87.      * @method checkReady 检查依赖列表中的模块文件是否都已就绪。 
  88.      * @desc 如果都就绪了,就开始进行遍历(假定队列中的每个模块都是顶节点,进行多叉树深度遍历)。 
  89.      */ 
  90.     function checkReady(){ 
  91.         if(!RequireList.join('')){ 
  92.             $each(ExecuteQueue.reverse(),traverse); 
  93.             ExecuteQueue.length = 0; 
  94.             RequireList.length = 0; 
  95.             RequireHash = {}; 
  96.         } 
  97.     } 
  98.  
  99.     /** 
  100.      * @method checkDepend 检查模块依赖文件是否已请求 
  101.      * @param {String} mName 模块名称 
  102.      */ 
  103.     function checkDepend(mName){ 
  104.         var requires = [].concat(RequireCache); 
  105.         RequireCache.length = 0; 
  106.         RequireTree[mName] = requires; 
  107.         $each(requires,function(name){ 
  108.             if(!JSRequested[name]){ 
  109.                 ExecuteQueue.unshift(name); 
  110.                 RequireHash[name] = RequireList.length; 
  111.                 RequireList.push(name); 
  112.                 loadModule( name ); 
  113.                 JSRequested[name] = true
  114.             } 
  115.         }); 
  116.         checkReady(); 
  117.     } 
  118.  
  119.     /** 
  120.      * @method register 注册一个模块 
  121.      * @param {String} mName 模块的名称 
  122.      * @param {Function} maker 模块构造函数 
  123.      */ 
  124.     function register(mName, maker){ 
  125.         if(ModuleCache[mName]){ 
  126.             throw('The "' + mName + '" has been registered!!!'); 
  127.         } 
  128.         ModuleCache[mName] = maker; 
  129.         if(!JSRequested[mName]){ 
  130.             ExecuteQueue.push(mName); 
  131.             JSRequested[mName] = true
  132.         } 
  133.         if(RequireHash[mName] !== undefined){ 
  134.             RequireList[ RequireHash[mName] ] = ''
  135.         } 
  136.         checkDepend(mName); 
  137.     } 
  138.  
  139.     /** 
  140.      * @method $Import 加载(引入)一个模块 
  141.      * @param {String} name 模块的名称 
  142.      */ 
  143.     function $Import(name){ 
  144.         RequireCache.push(name); 
  145.     } 
  146.  
  147. //--------------------------- 
  148.     //参数配置区: 
  149.     var spaceName = 'SPORE';                //定义顶层命名空间的名称 
  150.     var managerName = '$JSManager';         //要暴露出去的JS管理器的名称 
  151.     var regName = 'register';               //要暴露出去的注册组件函数的名称 
  152.     var impName = '$Import';                //要暴露出去的引用组件函数的名称 
  153.     JSManager.$basepath = 'js/';            //JS库所在的线上路径 
  154.     JSManager.$charset = 'utf-8';           //JS文件的编码    
  155.     JSManager.$version = new Date() - 0;    //JS版本号 
  156. //--------------------------- 
  157.  
  158.     //适配器: 
  159.     that[impName] = $Import; 
  160.     that[regName] = register; 
  161.     that[managerName] = JSManager; 
  162.     ModuleCache[impName] = ModuleCache[regName] = ModuleCache[managerName] = 'ready'
  163.     JSManager.$ModuleCache = ModuleCache; 
  164.     JSManager.$RequireList = RequireList; 
  165.     JSManager.$ModuleLoaded = ModuleLoaded; 
  166.     if(!this[spaceName]){ 
  167.         this[spaceName] = that; 
  168.     }else
  169.         $each(that, function(prop, name){ 
  170.             if(!this[spaceName][name]){ 
  171.                 this[spaceName][name] = prop; 
  172.             }else
  173.                 throw('Lib "' + spaceName + '" exists the same prop: ' + name); 
  174.             } 
  175.         }); 
  176.         that = this[spaceName]; 
  177.     } 
  178.  
  179. })(); 
  180.  
  181. /* 
  182. 上面的代码实现了命名空间管理和文件自动依赖。 
  183. 使用方法: 
  184. 1,修改代码配置信息,确认 
  185.     - JS库的命名,或者为哪个JS库扩展此功能(比如为jQuery扩展该功能)。 
  186.     - JS文件的编码(charset) 
  187.     - JS库所在的服务端路径 
  188.     - JS库的版本号(默认为当前客户端时间转为的数字,避免加载的JS被缓存)。 
  189. 2,在页面上引用这个JS。 
  190. 3,在JS库所在的文件夹添加扩展JS文件,文件结构类似这样: 
  191. //库的顶层位置spore.js 
  192.     - arr 
  193.         each.js 
  194.         erase.js 
  195.         ... 
  196.     - obj 
  197.         merge.js 
  198.         append.js 
  199.         ... 
  200.     - conf 
  201.         index.js 
  202.     - jobs 
  203.         loadpage.js 
  204.     -arr为一个文件夹,以''arr''命名,其他类似 
  205. 4,JS文件代码格式如下 
  206. */ 
  207.  
  208. //--------------------------------------------------------- 
  209. //obj/merge.js: 
  210. //命名空间构造函数 
  211. SPORE.register('obj.merge',function($){ 
  212.     return function(obj1,obj2){ //返回的函数将被创建到SPORE.obj.merge这个DOM位置。 
  213.     //功能代码 
  214.     } 
  215. }); 
  216. //--------------------------------------------------------- 
  217. //jobs/loadpage.js: 
  218. //引用列表 
  219. SPORE.$Import('obj.merge'); //参数为命名空间的名称 
  220. SPORE.$Import('arr.each'); 
  221.  
  222. //命名空间构造函数 
  223. SPORE.register('jobs.loadpage',function($){ //美元符号为对SPORE的引用 
  224.     var that = {}; 
  225.     var obj = $.obj.merge({a:1},{b:2}); //要使用这个函数,必须确保引用列表中存在引用代码(SPORE.$Import('obj.merge');) 
  226.     //... 业务逻辑 
  227.     return that;    //这个函数将在依赖的文件就绪后执行,返回值将被创建到SPORE.jobs.loadpage这个DOM位置。 
  228. }); 
  229. //--------------------------------------------------------- 

解决了模块循环引用造成的问题。

禁忌:不要让模块与文件夹同名。如:和arr文件夹平级的目录,不要出现arr.js。

通过服务端脚本解析里面的$Import函数可以实现文件服务端合并打包部署。

这个模块取自开源项目mootools-lib-cn,构思巧妙,实现JS命名空间的管理,模块的定义,与自动加载依赖模块的脚本文件,可以与Mootools、jQuery等框架搭配,扩展自己的JS库,很棒!!!

至于原作者说的不要让模块与文件夹同名这个禁忌,我试了一下,也没什么不妥。