AMD加载器实现笔记(五)
前几篇文章对AMD规范中的config属性几乎全部支持了,这一节主要是进一步完善。到目前为止我们的加载器还无法处理环形依赖的问题,这一节就是解决环形依赖。
所谓环形依赖,指的是模块A的所有依赖项的依赖中有没有依赖A模块本身的模块。如果有那就说明存在环形依赖。所以检验的方式是利用递归,检查一个模块的依赖的依赖项中有没有依赖A模块,以及依赖项的依赖项的依赖项中有没有A模块,核心代码如下:
function checkCircleRef(start, target){
        var m = modules[start];
        if (!m) {
            return false;
        }
        var depModules = m.deps.map(function(dep) {
            return modules[dep] || null;
        });
        return depModules.some(function(m) {//检查依赖项的依赖项
            if (!m) {
                return false;
            }
            return m.deps.some(function(dep) {
                var equal = dep === target;
                if (equal) {
                    console.error("circle reference: ", target, m.id);
                }
                return equal;
            });
        }) ? true : depModules.some(function(m) {//检查依赖项的依赖项的依赖项
            if (!m) {
                return false;
            }
            return m.deps.some(function(dep) {
                return checkCircleRef(dep, target);
            });
        });
    };
剩下的问题是我们把检查放到哪里去?我们的模块最先在require中注册,所以最佳的位置是放在require函数中去检查:
//require 函数
//。。。。。。。
var module = {
id: id,
deps: deps,
factory: callback,
state: 1,
result: null
};
modules[id] = module; if (checkCircleRef(id, id)) {
hasCircleReferece = true;
return;
}
// ......................
//.......................
//......................
下一个要面临的问题就是:如果存在依赖项如何处理?对此,我的做法是如果存在环形依赖,结束整个加载过程。我们在加载器内部使用一个哨兵变量,一旦存在环形依赖,停止所有工作。如:loadJs中:
script.onload = function() {
            if (hasCircleReferece) {
                return;
            }
            var module = modules[url];
            if (module && isReady(module) && loadings.indexOf(url) > -1) {
                callFactory(module);
            }
            checkDeps();
        };
如define函数中:
if (modules[id]) {
            console.error('multiple define module: ' + id);
        }
        if (!hasCircleReferece) {
            require(deps, callback, id);
        }
测试:
require.config({
    baseUrl: "./",
    packages: [{
        name: "more",
        location: "./more"
    }, {
        name: "mass",
        location: "../"
    }, {
        name: "wab",
        location: "../../../"
    }],
    shim: {
        "something": {
            "deps": ['jquery'],
            exports: 'something',
            init: function(jq, ol) {
                console.log(jq);
                console.log($);
                return something + " in shim";
            }
        }
    },
    map: {
        '*': {
            'jquery': 'jquery-private'
        },
        'jquery-private': {
            'jquery': 'jquery'
        }
    },
    paths: {
        'jquery': "../../Bodhi/src/roots/jquery"
    }
  });
  require([
  'bbb',
  'aaa.bbb.ccc',
  'ccc',
  'ddd',
  'fff',
  'something'
  ], function(aaabbbccc){
    console.log('simple loader');
    console.log(arguments);
  });
bbb中:
define(["aaa",
"ccc"],function(a, c){
console.log("已加载bbb模块", 7)
return {
aaa: a,
ccc: c.ccc,
bbb: "bbb"
}
})
aaa中:
define(['bbb'], function(){
    console.log("已加载aaa模块", 7)
    return "aaa"
});
测试结果:
circle reference: simpleAMDLoader/aaa simpleAMDLoader/bbb
目前为止整个加载器代码如下:
 (function(global){
     global = global || window;
     var modules = {};
     var loadings = [];
     var loadedJs = [];
     var hasCircleReferece = false;
     //module: id, state, factory, result, deps;
     global.require = function(deps, callback, parent){
         var id = parent || "Bodhi" + Date.now();
         var cn = 0, dn = deps.length;
         var args = [];
         var oriDeps = deps.slice();//保留原始dep的模块Id
          // dep为非绝对路径形式,而modules的key仍然需要绝对路径
         deps = deps.map(function(dep) {
             if (modules[dep]) { //jquery
                 return dep;
             } else if (dep in global.require.parsedConfig.paths) {
                 return dep;
             }
             var rel = "";
             if (/^Bodhi/.test(id)) {
                 rel = global.require.parsedConfig.baseUrl;
             } else {
                 var parts = parent.split('/');
                 parts.pop();
                 rel = parts.join('/');
             }
             return getModuleUrl(dep, rel);
         });
         var module = {
             id: id,
             deps: deps,
             factory: callback,
             state: 1,
             result: null
         };
         modules[id] = module;
         if (checkCircleRef(id, id)) {
             hasCircleReferece = true;
             return;
         }
         //checkCircleRef(id, id)
         deps.forEach(function(dep, i) {
             if (modules[dep] && modules[dep].state === 2) {
                 cn++
                 args.push(modules[dep].result);
             } else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {
                 loadJS(dep, oriDeps[i]);
                 loadedJs.push(dep);
             }
         });
         if (cn === dn) {
             callFactory(module);
         } else {
             loadings.push(id);
             checkDeps();
         }
     };
     global.require.config = function(config) {
         this.parsedConfig = {};
         if (config.baseUrl) {
             var currentUrl = getCurrentScript();
             var parts = currentUrl.split('/');
             parts.pop();
             var currentDir = parts.join('/');
             this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
         }
         var burl = this.parsedConfig.baseUrl;
         // 得到baseUrl后,location相对baseUrl定位
         this.parsedConfig.packages = [];
         if (config.packages) {
             for (var i = 0, len = config.packages.length; i < len; i++) {
                 var pck = config.packages[i];
                 var cp = {
                     name: pck.name,
                     location: getRoute(burl, pck.location)
                 }
                 this.parsedConfig.packages.push(cp);
             }
         }
         this.parsedConfig.paths = {};
         if (config.paths) {
             for (var p in config.paths) {
                 this.parsedConfig.paths[p] = /^http(s)?/.test(config.paths[p]) ? config.paths[p] : getRoute(burl, config.paths[p]);
             }
         }
         this.parsedConfig.map = {};
         if (config.map) {
             this.parsedConfig.map = config.map;
         }
         this.parsedConfig.shim = {};
         //shim 要放在最后处理
         if (config.shim) {
             this.parsedConfig.shim = config.shim;
             for (var p in config.shim) {
                 var item = config.shim[p];
                 define(p, item.deps, function() {
                     var exports;
                     if (item.init) {
                         exports = item.init.apply(item, arguments);
                     }
                     return exports ? exports : item.exports;
                 });
             }
         }
         console.log(this.parsedConfig);
     }
     global.define = function(id, deps, callback) {
         //加上moduleId的支持
         if (typeof id !== "string" && arguments.length === 2) {
             callback = deps;
             deps = id;
             id = "";
         }
         var id = id || getCurrentScript();
         var mId = getModuleId(id);
         if (mId || id in require.parsedConfig.shim) {
             mId = mId ? mId : id;
             var maping = getMapSetting(mId);
             if (maping) {
                 deps = deps.map(function(dep) {
                     return maping[dep] || dep;
                 });
             }
         }
         if (modules[id]) {
             console.error('multiple define module: ' + id);
         }
         if (!hasCircleReferece) {
             require(deps, callback, id);
         }
     };
     global.define.amd = {};//AMD规范
     function getModuleId(url) {
         var script = document.querySelector('script[src="' + url + '"]');
         if (script) {
             return script.getAttribute('data-moduleId');
         } else {
             return null;
         }
     };
     function getMapSetting(mId) {
         if (mId in require.parsedConfig.map) {
             return require.parsedConfig[mId];
         } else if ('*' in require.parsedConfig.map) {
             return require.parsedConfig.map['*'];
         } else {
             return null;
         }
     };
     function checkCircleRef(start, target){
         var m = modules[start];
         if (!m) {
             return false;
         }
         var depModules = m.deps.map(function(dep) {
             return modules[dep] || null;
         });
         return depModules.some(function(m) {
             if (!m) {
                 return false;
             }
             return m.deps.some(function(dep) {
                 var equal = dep === target;
                 if (equal) {
                     console.error("circle reference: ", target, m.id);
                 }
                 return equal;
             });
         }) ? true : depModules.some(function(m) {
             if (!m) {
                 return false;
             }
             return m.deps.some(function(dep) {
                 return checkCircleRef(dep, target);
             });
         });
     };
     function getRoute(base, target) {
         var bts = base.replace(/\/$/, "").split('/');  //base dir
         var tts = target.split('/'); //target parts
         while (isDefined(tts[0])) {
             if (tts[0] === '.') {
                 return bts.join('/') + '/' + tts.slice(1).join('/');
             } else if (tts[0] === '..') {
                 bts.pop();
                 tts.shift();
             } else {
                 return bts.join('/') + '/' + tts.join('/');
             }
         }
     };
     function isDefined(v) {
         return v !== null && v !== undefined;
     };
     function getModuleUrl(moduleId, relative) {
         function getPackage(nm) {
             for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
                 var pck = require.parsedConfig.packages[i];
                 if (nm === pck.name) {
                     return pck;
                 }
             }
             return false;
         }
         var mts = moduleId.split('/');
         var pck = getPackage(mts[0]);
         if (pck) {
             mts.shift();
             return getRoute(pck.location, mts.join('/'));
         } else if (mts[0] === '.' || mts[0] === '..') {
             return getRoute(relative, moduleId);
         } else {
             return getRoute(require.parsedConfig.baseUrl, moduleId);
         }
     };
     function loadJS(url, mId) {
         var script = document.createElement('script');
         script.setAttribute('data-moduleId', mId); //为script元素保留原始模块Id
         script.type = "text/javascript";
         //判断模块是否在paths中定义了路径
         script.src = (url in global.require.parsedConfig.paths ? global.require.parsedConfig.paths[url] : url) + '.js';
         script.onload = function() {
             if (hasCircleReferece) {
                 return;
             }
             var module = modules[url];
             if (module && isReady(module) && loadings.indexOf(url) > -1) {
                 callFactory(module);
             }
             checkDeps();
         };
         var head = document.getElementsByTagName('head')[0];
         head.appendChild(script);
     };
     function checkDeps() {
         for (var p in modules) {
             var module = modules[p];
             if (isReady(module) && loadings.indexOf(module.id) > -1) {
                 callFactory(module);
                 checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功
             }
         }
     };
     function isReady(m) {
         var deps = m.deps;
         var allReady = deps.every(function(dep) {
             return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;
         })
         if (deps.length === 0 || allReady) {
             return true;
         }
     };
     function callFactory(m) {
         var args = [];
         for (var i = 0, len = m.deps.length; i < len; i++) {
             args.push(modules[m.deps[i]].result);
         }
         m.result = m.factory.apply(window, args);
         m.state = 2;
         var idx = loadings.indexOf(m.id);
         if (idx > -1) {
             loadings.splice(idx, 1);
         }
     };
     function getCurrentScript(base) {
         // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
         var stack;
         try {
             a.b.c(); //强制报错,以便捕获e.stack
         } catch (e) { //safari的错误对象只有line,sourceId,sourceURL
             stack = e.stack;
             if (!stack && window.opera) {
                 //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取
                 stack = (String(e).match(/of linked script \S+/g) || []).join(" ");
             }
         }
         if (stack) {
             /**e.stack最后一行在所有支持的浏览器大致如下:
              *chrome23:
              * at http://113.93.50.63/data.js:4:1
              *firefox17:
              *@http://113.93.50.63/query.js:4
              *opera12:http://www.oldapps.com/opera.php?system=Windows_XP
              *@http://113.93.50.63/data.js:4
              *IE10:
              *  at Global code (http://113.93.50.63/data.js:4:1)
              *  //firefox4+ 可以用document.currentScript
              */
             stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分
             stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, ""); //去掉换行符
             return stack.replace(/(:\d+)?:\d+$/i, "").replace(/\.js$/, ""); //去掉行号与或许存在的出错字符起始位置
         }
         var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找
         for (var i = nodes.length, node; node = nodes[--i]; ) {
             if ((base || node.className === moduleClass) && node.readyState === "interactive") {
                 return node.className = node.src;
             }
         }
     };
 })(window)
AMD加载器实现笔记(五)的更多相关文章
- AMD加载器实现笔记(二)
		AMD加载器实现笔记(一)中,我们实现了一个简易的模块加载器.但到目前为止这个加载器还并不能称为AMD加载器,原因很简单,我们还不支持AMD规范中的config配置.这篇文章中我们来添加对config ... 
- AMD加载器实现笔记(一)
		之前研究过AMD,也写过一篇关于AMD的文章<以代码爱好者角度来看AMD与CMD>.代码我是有看过的,基本的原理也都明白,但实际动手去实现却是没有的.因为今年计划的dojo教程<静静 ... 
- AMD加载器实现笔记(四)
		继续这一系列的内容,到目前为止除了AMD规范中config的map.config参数外,我们已经全部支持其他属性了.这一篇文章中,我们来为增加对map的支持.同样问题,想要增加map的支持首先要知道m ... 
- AMD加载器实现笔记(三)
		上一篇文章中我们为config添加了baseUrl和packages的支持,那么这篇文章中将会看到对shim与paths的支持. 要添加shim与paths,第一要务当然是了解他们的语义与用法.先来看 ... 
- AngularJs2与AMD加载器(dojo requirejs)集成
		现在是西太平洋时间凌晨,这个问题我鼓捣了一天,都没时间学英语了,英语太差,相信第二天我也看不懂了,直接看结果就行. 核心原理就是require在AngularJs2编译过程中是关键字,而在浏览器里面运 ... 
- Promise实现简易AMD加载器
		在最新的Chrome和FF中已经 实现了Promise.有了Promise我们用数行代码即可实现一个简易AMD模式的加载器 var registry = { promises: { }, resolv ... 
- JavaScript AMD 模块加载器原理与实现
		关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者 ... 
- KnockoutJS 3.X API 第六章 组件(5) 高级应用组件加载器
		无论何时使用组件绑定或自定义元素注入组件,Knockout都将使用一个或多个组件装载器获取该组件的模板和视图模型. 组件加载器的任务是异步提供任何给定组件名称的模板/视图模型对. 本节目录 默认组件加 ... 
- 构建服务端的AMD/CMD模块加载器
		本文原文地址:http://trock.lofter.com/post/117023_1208040 . 引言: 在前端开发领域,相信大家对AMD/CMD规范一定不会陌生,尤其对requireJS. ... 
随机推荐
- 近期微博吐槽言论存档,涉及“性能优化”、C++陋习等
			写C++程序的几个陋习:class 名以大写 C 开头,例如 CDate:成员变量以 m_ 开头:变量采用匈牙利命名法:不知道何时禁用 copy-ctor/assign operator.前三个可能是 ... 
- SQL触发器实例讲解
			SQL触发器实例1 定义: 何为触发器?在SQL Server里面也就是对某一个表的一定的操作,触发某种条件,从而执行的一段程序.触发器是一个特殊的存储过程. 常见的触发器有三种:分别应用于Inser ... 
- list对象属性排序
			Collections.sort(list, new Comparator<ScRel>() { @Override public int compare(Object o1, Objec ... 
- terminal崩溃打不开的一种原因以及ubuntu下matlab权限不够的解决办法
			为了解决点击matlab图标闪退的问题,我往.bashrc添加了如下命令: source /usr/local/MATLAB/R2015b/bin/matlab 结果导致打开新的terminal闪退. ... 
- px4flow通过iic读取具体寄存器数据程序
			底层通信用了昨天写好的iic,今天结合官方资料成功读出所有指定寄存器的数据附上源码 include.h主要包括了一些stm32 IO控制的宏定义,具体参考正点原子所有例程中都有的sys.h头文件 in ... 
- 关于c++
			http://www.ezlippi.com/blog/2014/12/c-open-project.html 
- 核心动画(CAKeyframeAnimation)
			Main.storyboard ViewController.m // // ViewController.m // 8A02.核心动画 - CAKeyframeAnimation // // ... 
- React Native填坑之旅--布局篇
			代码在这里: https://github.com/future-challenger/petshop/tree/master/client/petshop/src/controller 回头看看RN ... 
- 【开发环境】OFFICE 完全卸载工具(微软)
			OFFICE没有正确安装,每次打开OFFICE都会提示: “The setup controller has encountered a problem during instll.Please re ... 
- ExtJS 列表数据编辑
			在ExtJs中,GridPanel一般用于展示列表数据.同时利用一些附加的插件也能编辑数据.类似于asp.net中的DataGridView控件. 展示数据比较简单,利用Store则可以自动展示,只要 ... 
