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. ...
随机推荐
- SQLYog快捷键大全
Ctrl+M 创建一个新的连接 Ctrl+N 使用当前设置新建连接 Ctrl+F4 断开当前连接 对象浏览器 F5 刷新对象浏览器(默认) Ctrl+B 设置焦点于对象浏览器 SQ ...
- C语言实现 字符串过滤并修改并返回个数
基本问题:给定一个strContent,strWord,使用strWord 匹配strContent,匹配成功,将匹配部分全部替换为‘*’ ,并返回匹配成功个数.注意不能使用库函数. 例如:strCo ...
- Linux的学习之路
linux的安装配置.常用命令: 基本上学习任务: 1.在VMware(9)虚拟机上安装Linux操作系统(安装CentOs操作系统) 2.了解Linux操作系统 3.通过XShell工具操作Linu ...
- php二维数组按照键值排序的方法
//按照传入数组中的num倒序 public function numdesc($array,$key="num",$order="desc"){ $arr_n ...
- c++ 课堂作业(1)
一.题目 Create a program that asks for the radius of a circle and prints the area of that circle, using ...
- jquery easyui的treegrid的控制
其中列的formatter很有作用,可以得到你想要的任何内容: 例: <table class="easyui-treegrid" id="tg" dat ...
- Tomcat7下出现The requested resource(/)is not available
1首先确保你的localhost是否正常运行解决方案:1观察项目是否部署2重新将tomcat7导入 2确保你的项目名后跟index.jsp是否正常运行解决方案:1右键项目名,web进行查询,观察部署的 ...
- Struts1 action重定向跳转 带参数
ActionForward forward = new ActionForward("kmRentalMain.do?method=view&fdId="+id);forw ...
- Fresco简单的使用—SimpleDraweeView
本文出处:http://blog.csdn.net/u011164565/article/details/51330778 Fresco是一个第三方库,github官网地址:https://githu ...
- 编译器工具 Flex Bison for Windows 简单入门例子
最近从事一个系统仿真软件的开发,里面定义了自己的描述性语言MSL, MSL语言经FlexBison转换成C语言,然后用C编译器来编译并计算仿真. 现在领域驱动开发比较热门,有机会定义自己的语言对程序员 ...