前几篇文章对AMD规范中的config属性几乎全部支持了,这一节主要是进一步完善。到目前为止我们的加载器还无法处理环形依赖的问题,这一节就是解决环形依赖。

  所谓环形依赖,指的是模块A的所有依赖项的依赖中有没有依赖A模块本身的模块。如果有那就说明存在环形依赖。所以检验的方式是利用递归,检查一个模块的依赖的依赖项中有没有依赖A模块,以及依赖项的依赖项的依赖项中有没有A模块,核心代码如下:

  1. function checkCircleRef(start, target){
  2. var m = modules[start];
  3. if (!m) {
  4. return false;
  5. }
  6. var depModules = m.deps.map(function(dep) {
  7. return modules[dep] || null;
  8. });
  9.  
  10. return depModules.some(function(m) {//检查依赖项的依赖项
  11. if (!m) {
  12. return false;
  13. }
  14. return m.deps.some(function(dep) {
  15. var equal = dep === target;
  16. if (equal) {
  17. console.error("circle reference: ", target, m.id);
  18. }
  19.  
  20. return equal;
  21. });
  22. }) ? true : depModules.some(function(m) {//检查依赖项的依赖项的依赖项
  23. if (!m) {
  24. return false;
  25. }
  26. return m.deps.some(function(dep) {
  27. return checkCircleRef(dep, target);
  28. });
  29. });
  30. };

  

  剩下的问题是我们把检查放到哪里去?我们的模块最先在require中注册,所以最佳的位置是放在require函数中去检查:

  1. //require 函数
  2. //。。。。。。。
  3. var module = {
  4. id: id,
  5. deps: deps,
  6. factory: callback,
  7. state: 1,
  8. result: null
  9. };
  10. modules[id] = module;
  11.  
  12. if (checkCircleRef(id, id)) {
  13. hasCircleReferece = true;
  14. return;
  15. }
  16. // ......................
  17. //.......................
  18. //......................

  下一个要面临的问题就是:如果存在依赖项如何处理?对此,我的做法是如果存在环形依赖,结束整个加载过程。我们在加载器内部使用一个哨兵变量,一旦存在环形依赖,停止所有工作。如:loadJs中:

  1. script.onload = function() {
  2. if (hasCircleReferece) {
  3. return;
  4. }
  5. var module = modules[url];
  6. if (module && isReady(module) && loadings.indexOf(url) > -1) {
  7. callFactory(module);
  8. }
  9. checkDeps();
  10. };

  如define函数中:

  1. if (modules[id]) {
  2. console.error('multiple define module: ' + id);
  3. }
  4.  
  5. if (!hasCircleReferece) {
  6. require(deps, callback, id);
  7. }

  测试:

  1. require.config({
  2. baseUrl: "./",
  3. packages: [{
  4. name: "more",
  5. location: "./more"
  6. }, {
  7. name: "mass",
  8. location: "../"
  9. }, {
  10. name: "wab",
  11. location: "../../../"
  12. }],
  13. shim: {
  14. "something": {
  15. "deps": ['jquery'],
  16. exports: 'something',
  17. init: function(jq, ol) {
  18. console.log(jq);
  19. console.log($);
  20. return something + " in shim";
  21. }
  22. }
  23. },
  24. map: {
  25. '*': {
  26. 'jquery': 'jquery-private'
  27. },
  28. 'jquery-private': {
  29. 'jquery': 'jquery'
  30. }
  31. },
  32. paths: {
  33. 'jquery': "../../Bodhi/src/roots/jquery"
  34. }
  35. });
  36. require([
  37. 'bbb',
  38. 'aaa.bbb.ccc',
  39. 'ccc',
  40. 'ddd',
  41. 'fff',
  42. 'something'
  43. ], function(aaabbbccc){
  44. console.log('simple loader');
  45. console.log(arguments);
  46. });

  bbb中:

  1. define(["aaa",
  2. "ccc"],function(a, c){
  3. console.log("已加载bbb模块", 7)
  4. return {
  5. aaa: a,
  6. ccc: c.ccc,
  7. bbb: "bbb"
  8. }
  9. })

  aaa中:

  1. define(['bbb'], function(){
  2. console.log("已加载aaa模块", 7)
  3. return "aaa"
  4. });

  测试结果:

circle reference:  simpleAMDLoader/aaa simpleAMDLoader/bbb

  目前为止整个加载器代码如下:

  1. (function(global){
  2. global = global || window;
  3. var modules = {};
  4. var loadings = [];
  5. var loadedJs = [];
  6. var hasCircleReferece = false;
  7. //module: id, state, factory, result, deps;
  8. global.require = function(deps, callback, parent){
  9. var id = parent || "Bodhi" + Date.now();
  10. var cn = 0, dn = deps.length;
  11. var args = [];
  12.  
  13. var oriDeps = deps.slice();//保留原始dep的模块Id
  14.  
  15. // dep为非绝对路径形式,而modules的key仍然需要绝对路径
  16. deps = deps.map(function(dep) {
  17. if (modules[dep]) { //jquery
  18. return dep;
  19. } else if (dep in global.require.parsedConfig.paths) {
  20. return dep;
  21. }
  22. var rel = "";
  23. if (/^Bodhi/.test(id)) {
  24. rel = global.require.parsedConfig.baseUrl;
  25. } else {
  26. var parts = parent.split('/');
  27. parts.pop();
  28. rel = parts.join('/');
  29. }
  30. return getModuleUrl(dep, rel);
  31. });
  32.  
  33. var module = {
  34. id: id,
  35. deps: deps,
  36. factory: callback,
  37. state: 1,
  38. result: null
  39. };
  40. modules[id] = module;
  41.  
  42. if (checkCircleRef(id, id)) {
  43. hasCircleReferece = true;
  44. return;
  45. }
  46. //checkCircleRef(id, id)
  47.  
  48. deps.forEach(function(dep, i) {
  49. if (modules[dep] && modules[dep].state === 2) {
  50. cn++
  51. args.push(modules[dep].result);
  52. } else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {
  53. loadJS(dep, oriDeps[i]);
  54. loadedJs.push(dep);
  55. }
  56. });
  57. if (cn === dn) {
  58. callFactory(module);
  59. } else {
  60. loadings.push(id);
  61. checkDeps();
  62. }
  63. };
  64.  
  65. global.require.config = function(config) {
  66. this.parsedConfig = {};
  67. if (config.baseUrl) {
  68. var currentUrl = getCurrentScript();
  69. var parts = currentUrl.split('/');
  70. parts.pop();
  71. var currentDir = parts.join('/');
  72. this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
  73. }
  74. var burl = this.parsedConfig.baseUrl;
  75. // 得到baseUrl后,location相对baseUrl定位
  76. this.parsedConfig.packages = [];
  77. if (config.packages) {
  78. for (var i = 0, len = config.packages.length; i < len; i++) {
  79. var pck = config.packages[i];
  80. var cp = {
  81. name: pck.name,
  82. location: getRoute(burl, pck.location)
  83. }
  84. this.parsedConfig.packages.push(cp);
  85. }
  86. }
  87.  
  88. this.parsedConfig.paths = {};
  89. if (config.paths) {
  90. for (var p in config.paths) {
  91. this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);
  92. }
  93. }
  94.  
  95. this.parsedConfig.map = {};
  96. if (config.map) {
  97. this.parsedConfig.map = config.map;
  98. }
  99.  
  100. this.parsedConfig.shim = {};
  101. //shim 要放在最后处理
  102. if (config.shim) {
  103. this.parsedConfig.shim = config.shim;
  104. for (var p in config.shim) {
  105. var item = config.shim[p];
  106. define(p, item.deps, function() {
  107. var exports;
  108. if (item.init) {
  109. exports = item.init.apply(item, arguments);
  110. }
  111.  
  112. return exports ? exports : item.exports;
  113. });
  114. }
  115. }
  116.  
  117. console.log(this.parsedConfig);
  118. }
  119.  
  120. global.define = function(id, deps, callback) {
  121. //加上moduleId的支持
  122. if (typeof id !== "string" && arguments.length === 2) {
  123. callback = deps;
  124. deps = id;
  125. id = "";
  126. }
  127. var id = id || getCurrentScript();
  128.  
  129. var mId = getModuleId(id);
  130. if (mId || id in require.parsedConfig.shim) {
  131. mId = mId ? mId : id;
  132. var maping = getMapSetting(mId);
  133.  
  134. if (maping) {
  135. deps = deps.map(function(dep) {
  136. return maping[dep] || dep;
  137. });
  138. }
  139. }
  140. if (modules[id]) {
  141. console.error('multiple define module: ' + id);
  142. }
  143.  
  144. if (!hasCircleReferece) {
  145. require(deps, callback, id);
  146. }
  147. };
  148.  
  149. global.define.amd = {};//AMD规范
  150.  
  151. function getModuleId(url) {
  152. var script = document.querySelector('script[src="' + url + '"]');
  153. if (script) {
  154. return script.getAttribute('data-moduleId');
  155. } else {
  156. return null;
  157. }
  158. };
  159.  
  160. function getMapSetting(mId) {
  161. if (mId in require.parsedConfig.map) {
  162. return require.parsedConfig[mId];
  163. } else if ('*' in require.parsedConfig.map) {
  164. return require.parsedConfig.map['*'];
  165. } else {
  166. return null;
  167. }
  168. };
  169.  
  170. function checkCircleRef(start, target){
  171. var m = modules[start];
  172. if (!m) {
  173. return false;
  174. }
  175. var depModules = m.deps.map(function(dep) {
  176. return modules[dep] || null;
  177. });
  178.  
  179. return depModules.some(function(m) {
  180. if (!m) {
  181. return false;
  182. }
  183. return m.deps.some(function(dep) {
  184. var equal = dep === target;
  185. if (equal) {
  186. console.error("circle reference: ", target, m.id);
  187. }
  188.  
  189. return equal;
  190. });
  191. }) ? true : depModules.some(function(m) {
  192. if (!m) {
  193. return false;
  194. }
  195. return m.deps.some(function(dep) {
  196. return checkCircleRef(dep, target);
  197. });
  198. });
  199. };
  200.  
  201. function getRoute(base, target) {
  202. var bts = base.replace(/\/$/, "").split('/'); //base dir
  203. var tts = target.split('/'); //target parts
  204. while (isDefined(tts[0])) {
  205. if (tts[0] === '.') {
  206. return bts.join('/') + '/' + tts.slice(1).join('/');
  207. } else if (tts[0] === '..') {
  208. bts.pop();
  209. tts.shift();
  210. } else {
  211. return bts.join('/') + '/' + tts.join('/');
  212. }
  213. }
  214. };
  215.  
  216. function isDefined(v) {
  217. return v !== null && v !== undefined;
  218. };
  219.  
  220. function getModuleUrl(moduleId, relative) {
  221. function getPackage(nm) {
  222. for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
  223. var pck = require.parsedConfig.packages[i];
  224. if (nm === pck.name) {
  225. return pck;
  226. }
  227. }
  228. return false;
  229. }
  230. var mts = moduleId.split('/');
  231. var pck = getPackage(mts[0]);
  232. if (pck) {
  233. mts.shift();
  234. return getRoute(pck.location, mts.join('/'));
  235. } else if (mts[0] === '.' || mts[0] === '..') {
  236. return getRoute(relative, moduleId);
  237. } else {
  238. return getRoute(require.parsedConfig.baseUrl, moduleId);
  239. }
  240. };
  241.  
  242. function loadJS(url, mId) {
  243. var script = document.createElement('script');
  244. script.setAttribute('data-moduleId', mId); //为script元素保留原始模块Id
  245. script.type = "text/javascript";
  246. //判断模块是否在paths中定义了路径
  247. script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + '.js';
  248. script.onload = function() {
  249. if (hasCircleReferece) {
  250. return;
  251. }
  252. var module = modules[url];
  253. if (module && isReady(module) && loadings.indexOf(url) > -1) {
  254. callFactory(module);
  255. }
  256. checkDeps();
  257. };
  258. var head = document.getElementsByTagName('head')[0];
  259. head.appendChild(script);
  260. };
  261.  
  262. function checkDeps() {
  263. for (var p in modules) {
  264. var module = modules[p];
  265. if (isReady(module) && loadings.indexOf(module.id) > -1) {
  266. callFactory(module);
  267. checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功
  268. }
  269. }
  270. };
  271.  
  272. function isReady(m) {
  273. var deps = m.deps;
  274. var allReady = deps.every(function(dep) {
  275. return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;
  276. })
  277. if (deps.length === 0 || allReady) {
  278. return true;
  279. }
  280. };
  281.  
  282. function callFactory(m) {
  283. var args = [];
  284. for (var i = 0, len = m.deps.length; i < len; i++) {
  285. args.push(modules[m.deps[i]].result);
  286. }
  287. m.result = m.factory.apply(window, args);
  288. m.state = 2;
  289.  
  290. var idx = loadings.indexOf(m.id);
  291. if (idx > -1) {
  292. loadings.splice(idx, 1);
  293. }
  294. };
  295.  
  296. function getCurrentScript(base) {
  297. // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
  298. var stack;
  299. try {
  300. a.b.c(); //强制报错,以便捕获e.stack
  301. } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
  302. stack = e.stack;
  303. if (!stack && window.opera) {
  304. //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
  305. stack = (String(e).match(/of linked script \S+/g) || []).join(" ");
  306. }
  307. }
  308. if (stack) {
  309. /**e.stack最后一行在所有支持的浏览器大致如下:
  310. *chrome23:
  311. * at http://113.93.50.63/data.js:4:1
  312. *firefox17:
  313. *@http://113.93.50.63/query.js:4
  314. *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
  315. *@http://113.93.50.63/data.js:4
  316. *IE10:
  317. * at Global code (http://113.93.50.63/data.js:4:1)
  318. * //firefox4+ 可以用document.currentScript
  319. */
  320. stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分
  321. stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, ""); //去掉换行符
  322. return stack.replace(/(:\d+)?:\d+$/i, "").replace(/\.js$/, ""); //去掉行号与或许存在的出错字符起始位置
  323. }
  324. var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找
  325. for (var i = nodes.length, node; node = nodes[--i]; ) {
  326. if ((base || node.className === moduleClass) && node.readyState === "interactive") {
  327. return node.className = node.src;
  328. }
  329. }
  330. };
  331. })(window)

AMD加载器实现笔记(五)的更多相关文章

  1. AMD加载器实现笔记(二)

    AMD加载器实现笔记(一)中,我们实现了一个简易的模块加载器.但到目前为止这个加载器还并不能称为AMD加载器,原因很简单,我们还不支持AMD规范中的config配置.这篇文章中我们来添加对config ...

  2. AMD加载器实现笔记(一)

    之前研究过AMD,也写过一篇关于AMD的文章<以代码爱好者角度来看AMD与CMD>.代码我是有看过的,基本的原理也都明白,但实际动手去实现却是没有的.因为今年计划的dojo教程<静静 ...

  3. AMD加载器实现笔记(四)

    继续这一系列的内容,到目前为止除了AMD规范中config的map.config参数外,我们已经全部支持其他属性了.这一篇文章中,我们来为增加对map的支持.同样问题,想要增加map的支持首先要知道m ...

  4. AMD加载器实现笔记(三)

    上一篇文章中我们为config添加了baseUrl和packages的支持,那么这篇文章中将会看到对shim与paths的支持. 要添加shim与paths,第一要务当然是了解他们的语义与用法.先来看 ...

  5. AngularJs2与AMD加载器(dojo requirejs)集成

    现在是西太平洋时间凌晨,这个问题我鼓捣了一天,都没时间学英语了,英语太差,相信第二天我也看不懂了,直接看结果就行. 核心原理就是require在AngularJs2编译过程中是关键字,而在浏览器里面运 ...

  6. Promise实现简易AMD加载器

    在最新的Chrome和FF中已经 实现了Promise.有了Promise我们用数行代码即可实现一个简易AMD模式的加载器 var registry = { promises: { }, resolv ...

  7. JavaScript AMD 模块加载器原理与实现

    关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者 ...

  8. KnockoutJS 3.X API 第六章 组件(5) 高级应用组件加载器

    无论何时使用组件绑定或自定义元素注入组件,Knockout都将使用一个或多个组件装载器获取该组件的模板和视图模型. 组件加载器的任务是异步提供任何给定组件名称的模板/视图模型对. 本节目录 默认组件加 ...

  9. 构建服务端的AMD/CMD模块加载器

    本文原文地址:http://trock.lofter.com/post/117023_1208040 . 引言:  在前端开发领域,相信大家对AMD/CMD规范一定不会陌生,尤其对requireJS. ...

随机推荐

  1. SQLYog快捷键大全

    Ctrl+M   创建一个新的连接 Ctrl+N   使用当前设置新建连接 Ctrl+F4   断开当前连接 对象浏览器 F5   刷新对象浏览器(默认) Ctrl+B   设置焦点于对象浏览器 SQ ...

  2. C语言实现 字符串过滤并修改并返回个数

    基本问题:给定一个strContent,strWord,使用strWord 匹配strContent,匹配成功,将匹配部分全部替换为‘*’ ,并返回匹配成功个数.注意不能使用库函数. 例如:strCo ...

  3. Linux的学习之路

    linux的安装配置.常用命令: 基本上学习任务: 1.在VMware(9)虚拟机上安装Linux操作系统(安装CentOs操作系统) 2.了解Linux操作系统 3.通过XShell工具操作Linu ...

  4. php二维数组按照键值排序的方法

    //按照传入数组中的num倒序 public function numdesc($array,$key="num",$order="desc"){ $arr_n ...

  5. c++ 课堂作业(1)

    一.题目 Create a program that asks for the radius of a circle and prints the area of that circle, using ...

  6. jquery easyui的treegrid的控制

    其中列的formatter很有作用,可以得到你想要的任何内容: 例: <table class="easyui-treegrid" id="tg" dat ...

  7. Tomcat7下出现The requested resource(/)is not available

    1首先确保你的localhost是否正常运行解决方案:1观察项目是否部署2重新将tomcat7导入 2确保你的项目名后跟index.jsp是否正常运行解决方案:1右键项目名,web进行查询,观察部署的 ...

  8. Struts1 action重定向跳转 带参数

    ActionForward forward = new ActionForward("kmRentalMain.do?method=view&fdId="+id);forw ...

  9. Fresco简单的使用—SimpleDraweeView

    本文出处:http://blog.csdn.net/u011164565/article/details/51330778 Fresco是一个第三方库,github官网地址:https://githu ...

  10. 编译器工具 Flex Bison for Windows 简单入门例子

    最近从事一个系统仿真软件的开发,里面定义了自己的描述性语言MSL, MSL语言经FlexBison转换成C语言,然后用C编译器来编译并计算仿真. 现在领域驱动开发比较热门,有机会定义自己的语言对程序员 ...