在前文中咱们不止一次强调过模块化编程的重要性,以及其能够解决的问题:javascript
① 解决单文件变量命名冲突问题html
② 解决前端多人协做问题前端
③ 解决文件依赖问题java
④ 按需加载(这个说法其实很假了)ajax
⑤ ......算法
为了深刻了解加载器,中间阅读过一点requireJS的源码,但对于不少同窗来讲,对加载器的实现依旧不太清楚编程
事实上不经过代码实现,单单凭阅读想理解一个库或者框架只能达到只知其一;不知其二的地步,因此今天便来实现一个简单的加载器跨域
事实上,一个程序运行须要完整的模块,如下代码为例:数组
1 //求得绩效系数 2 var performanceCoefficient = function () { 3 return 0.2; 4 }; 5 6 //住房公积金计算方式 7 var companyReserve = function (salary) { 8 return salary * 0.2; 9 }; 10 11 //我的所得税 12 var incomeTax = function (salary) { 13 return salary * 0.2; 14 }; 15 16 //基本工资 17 var salary = 1000; 18 19 //最终工资 20 var mySalary = salary + salary * performanceCoefficient(); 21 mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary)); 22 console.log(mySalary);
我一份完整的工资来讲,公司会有绩效奖励,可是其算法可能很是复杂,其中可能涉及到出勤率,完成度什么的,这里暂时无论app
而有增便有减,因此咱们会交住房公积金,也会扣除我的所得税,最终才是个人工资
对于完整的程序来讲上面的流程缺一不可,可是各个函数中却有可能异常的复杂,跟钱有关系的东西都复杂,因此单单是公司绩效便有可能超过1000行代码
因而咱们这边便会开始分:
1 <script src="companyReserve.js" type="text/javascript"></script> 2 <script src="incomeTax.js" type="text/javascript"></script> 3 <script src="performanceCoefficient.js" type="text/javascript"></script> 4 <script type="text/javascript"> 5 6 //基本工资 7 var salary = 1000; 8 9 //最终工资 10 var mySalary = salary + salary * performanceCoefficient(); 11 mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary)); 12 console.log(mySalary); 13 14 </script>
上面的代码代表上是“分”开了,事实上也形成了“合”的问题,我要如何才能很好的把它们从新合到一块儿呢,毕竟其中的文件可能还涉及到依赖,这里便进入咱们的require与define
事实上,上面的方案仍然是以文件划分,而不是以模块划分的,如果文件名发生变化,页面会涉及到改变,其实这里应该有一个路径的映射处理这个问题
var pathCfg = { 'companyReserve': 'companyReserve', 'incomeTax': 'incomeTax', 'performanceCoefficient': 'performanceCoefficient' };
因而咱们一个模块便对应了一个路径js文件,剩下的即是将之对应模块的加载了,由于前端模块涉及到请求。因此这种写法:
companyReserve = requile('companyReserve');
对于前端来讲是不适用的,就算你在哪里看到这样作了,也必定是其中作了一些“手脚”,这里咱们便须要依据AMD规范了:
1 require.config({ 2 'companyReserve': 'companyReserve', 3 'incomeTax': 'incomeTax', 4 'performanceCoefficient': 'performanceCoefficient' 5 }); 6 7 require(['companyReserve', 'incomeTax', 'performanceCoefficient'], function (companyReserve, incomeTax, performanceCoefficient) { 8 //基本工资 9 var salary = 1000; 10 11 //最终工资 12 var mySalary = salary + salary * performanceCoefficient(); 13 mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary)); 14 console.log(mySalary); 15 });
这里即是一个标准的requireJS的写法了,首先定义模块以及其路径映射,其中定义依赖项
require(depArr, callback)
一个简单完整的模块加载器基本就是这个样子了,首先是一个依赖的数组,其次是一个回调,回调要求依赖项所有加载才能运行,而且回调的参数即是依赖项执行的结果,因此通常要求define模块具备一个返回值
方案有了,那么如何实现呢?
说到模块加载,人们第一反应都是ajax,由于不管什么时候,能拿到模块文件的内容,都是模块化的基本,可是采用ajax的方式是不行的,由于ajax有跨域的问题
而模块化方案又不可避免的要处理跨域的问题,因此使用动态建立script标签加载js文件便成为了首选,可是,不使用ajax的方案,对于实现难度来讲仍是有要求
PS:咱们实际工做中还会有加载html模板文件的场景,这个稍候再说
一般咱们是这样作的,require做为程序入口,调度javascript资源,而加载到各个define模块后,各个模块便悄无声息的建立script标签加载
加载结束后便往require模块队列报告本身加载结束了,当require中多有依赖模块皆加载结束时,便执行其回调
原理大体如此,剩下的只是具体实现,然后在论证这个理论是否靠谱便可
根据以上理论,咱们由总体来讲,首先以入口三个基本函数来讲
var require = function () { }; require.config = function () { }; require.define = function () { };
这三个模块比不可少:
① config用以配置模块与路径的映射,或者还有其余用处
② require为程序入口
③ define设计各个模块,响应require的调度
而后咱们这里会有一个建立script标签的方法,而且会监听其onLoad事件
④ loadScript
其次咱们加载script标签后,应该有一个全局的模块对象,用于存储已经加载好的模块,因而这里提出了两个需求:
⑤ require.moduleObj 模块存储对象
⑥ Module,模块的构造函数
有了以上核心模块,咱们造成了以下代码:
1 (function () { 2 3 var Module = function () { 4 this.status = 'loading'; //只具备loading与loaded两个状态 5 this.depCount = 0; //模块依赖项 6 this.value = null; //define函数回调执行的返回 7 }; 8 9 10 var loadScript = function (url, callback) { 11 12 }; 13 14 var config = function () { 15 16 }; 17 18 var require = function (deps, callback) { 19 20 }; 21 22 require.config = function (cfg) { 23 24 }; 25 26 var define = function (deps, callback) { 27 28 }; 29 30 })();
因而接下来即是具体实现,而后在实现过程当中补足不具有的接口与细节,每每在最后的实现与最初的设计没有半毛钱关系......
这块最初实现时,原本想直接参考requireJS的实现,可是咱们老大笑眯眯的拿出了一个他写的加载器,我一看不得不认可有点妖
因而这里便借鉴了其实现,作了简单改造:
1 (function () { 2 3 //存储已经加载好的模块 4 var moduleCache = {}; 5 6 var require = function (deps, callback) { 7 var params = []; 8 var depCount = 0; 9 var i, len, isEmpty = false, modName; 10 11 //获取当前正在执行的js代码段,这个在onLoad事件以前执行 12 modName = document.currentScript && document.currentScript.id || 'REQUIRE_MAIN'; 13 14 //简单实现,这里未作参数检查,只考虑数组的状况 15 if (deps.length) { 16 for (i = 0, len = deps.length; i < len; i++) { 17 (function (i) { 18 //依赖加一 19 depCount++; 20 //这块回调很关键 21 loadMod(deps[i], function (param) { 22 params[i] = param; 23 depCount--; 24 if (depCount == 0) { 25 saveModule(modName, params, callback); 26 } 27 }); 28 })(i); 29 } 30 } else { 31 isEmpty = true; 32 } 33 34 if (isEmpty) { 35 setTimeout(function () { 36 saveModule(modName, null, callback); 37 }, 0); 38 } 39 40 }; 41 42 //考虑最简单逻辑便可 43 var _getPathUrl = function (modName) { 44 var url = modName; 45 //不严谨 46 if (url.indexOf('.js') == -1) url = url + '.js'; 47 return url; 48 }; 49 50 //模块加载 51 var loadMod = function (modName, callback) { 52 var url = _getPathUrl(modName), fs, mod; 53 54 //若是该模块已经被加载 55 if (moduleCache[modName]) { 56 mod = moduleCache[modName]; 57 if (mod.status == 'loaded') { 58 setTimeout(callback(this.params), 0); 59 } else { 60 //若是未到加载状态直接往onLoad插入值,在依赖项加载好后会解除依赖 61 mod.onload.push(callback); 62 } 63 } else { 64 65 /* 66 这里重点说一下Module对象 67 status表明模块状态 68 onLoad事实上对应requireJS的事件回调,该模块被引用多少次变化执行多少次回调,通知被依赖项解除依赖 69 */ 70 mod = moduleCache[modName] = { 71 modName: modName, 72 status: 'loading', 73 export: null, 74 onload: [callback] 75 }; 76 77 _script = document.createElement('script'); 78 _script.id = modName; 79 _script.type = 'text/javascript'; 80 _script.charset = 'utf-8'; 81 _script.async = true; 82 _script.src = url; 83 84 //这段代码在这个场景中意义不大,注释了 85 // _script.onload = function (e) {}; 86 87 fs = document.getElementsByTagName('script')[0]; 88 fs.parentNode.insertBefore(_script, fs); 89 90 } 91 }; 92 93 var saveModule = function (modName, params, callback) { 94 var mod, fn; 95 96 if (moduleCache.hasOwnProperty(modName)) { 97 mod = moduleCache[modName]; 98 mod.status = 'loaded'; 99 //输出项 100 mod.export = callback ? callback(params) : null; 101 102 //解除父类依赖,这里事实上使用事件监听较好 103 while (fn = mod.onload.shift()) { 104 fn(mod.export); 105 } 106 } else { 107 callback && callback.apply(window, params); 108 } 109 }; 110 111 window.require = require; 112 window.define = require; 113 114 })();
首先这段代码有一些问题:
没有处理参数问题,字符串之类皆未处理
未处理循环依赖问题
未处理CMD写法
未处理html模板加载相关
未处理参数配置,baseUrl什么都没有搞
基于此想实现打包文件也不可能
......
但就是这100行代码,即是加载器的核心,代码很短,对各位理解加载器颇有帮助,里面有两点须要注意:
① requireJS是使用事件监听处理自己依赖,这里直接将之放到了onLoad数组中了
② 这里有一个颇有意思的东西
document.currentScript
这个能够获取当前执行的代码段
requireJS是在onLoad中处理各个模块的,这里就用了一个不同的实现,每一个js文件加载后,都会执行require(define)方法
执行后便取到当前正在执行的文件,而且取到文件名加载之,正由于如此,连script的onLoad事件都省了......
1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title></title> 4 </head> 5 <body> 6 </body> 7 <script src="require.js" type="text/javascript"></script> 8 <script type="text/javascript"> 9 require(['util', 'math', 'num'], function (util, math, num) { 10 11 num = math.getRadom() + '_' + num; 12 num = util.formatNum(num); 13 console.log(num); 14 }); 15 </script> 16 </html>
1 //util 2 define([], function () { 3 return { 4 formatNum: function (n) { 5 if (n < 10) return '0' + n; 6 return n; 7 } 8 }; 9 });
1 //math 2 define(['num'], function (num) { 3 return { 4 getRadom: function () { 5 return parseInt(Math.random() * num); 6 } 7 }; 8 });
1 //num 2 define([], function () { 3 return 10; 4 });
今天咱们实现了一个简单的模块加载器,经过他但愿能够帮助各位了解requireJS或者seaJS,最后顺利进入模块化编程的行列