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

  

  主要用于解决在两个不同模块集中使用一个模块的不同版本,并且保证两个模块集的交互没有冲突。

  

  假设磁盘有如下文件:

  

  当'some/newmodule'请求'foo'模块时,它将从foo1.2.js总得到'foo1.2'模块;当'some/oldmodule'请求'foo'模块时它将从foo1.0中得到'foo1.0'模块。

  在map属性中可以使用任何的module ID前缀,并且mapping对象可以匹配任何别的module ID前缀。

  

  如果出现通配符‘*’,表示任何模块使用这个匹配配置。通配符匹配对象中的模块ID前缀可以被覆盖。

  通过上文的解释,可以明白,如果在'some/newmodule'中依赖的foo实际是上依赖的foo1.2。转化成代码逻辑应当是这样的:如果在‘some/module’模块中发现依赖foo模块那就将foo替换成foo1.2。但是在什么地方实现替换好呢?因为模块的定义从define开始,同时只有在define中才能获得模块的绝对路径,所以我们把替换的处理放在define中。那么问题来了,我们的模块大部分都是匿名模块,模块自己如何知道自己的模块Id?所以一定要有一个标记去告诉define函数当前模块的Id,我们知道每一个模块都是一个JavaScript文件,每一个模块都有一个对应的script元素,所以最好的做法是没每一个script都加一个自定义特性,来标记当前元素的模块Id。

  所以在loadJs中要为script加自定义特性:

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() {
var module = modules[url];
if (module && isReady(module) && loadings.indexOf(url) > -1) {
callFactory(module);
}
checkDeps();
};
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
};

  在define函数中,通过文件的绝对路径,找出对应的script元素,拿到模块Id,判断如果在map中,则进行替换:

global.define = function(id, deps, callback) {
//加上moduleId的支持
if (typeof id !== "string" && arguments.length === 2) {
callback = deps;
deps = id;
id = "";
}
var id = id || getCurrentScript(); var script = document.querySelector('script[src="' + id + '"]');
if (script || id in require.parsedConfig.shim) {
var mId = script ? script.getAttribute('data-moduleId') : 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);
} require(deps, callback, id);
};
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;
}
};

  目前为止,我们的加载器已经支持了map属性,完整代码如下:

 (function(global){
global = global || window;
modules = {};
loadings = [];
loadedJs = [];
//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)) {
return;
} 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 script = document.querySelector('script[src="' + id + '"]');
if (script || id in require.parsedConfig.shim) {
var mId = script ? script.getAttribute('data-moduleId') : 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);
} require(deps, callback, id);
}; global.define.amd = {};//AMD规范 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);
});
}); //return hasCr ? true:
}; 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() {
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)

  

  下面我们看一个demo:

  使用我们的加载器来加载jquery,同时禁用jquery的全局模式$:

window.something = "Bodhi";
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);
});

  jquery-private代码如下:

define(['jquery'], function(jquery) {
return jquery.noConflict(true);
});

  如果某一模块依赖jquery,那么将会加载jquery-private。而在jquery中,因为配置了map和paths,所以jquery-private中的jquery根据paths找到jquery文件,并加载。同时在jquery-private中将禁用了全局模式之后的jquery对象返回给something模块。通过这个配置所有的模块在引用jquery时,实际上是引用了jquery-private模块。

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

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

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

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

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

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

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

  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. Slight difference between C++ and C

    In C++, results of assignment operation, prefix increment and prefix decrement are all lvalues, the ...

  2. python 类变量 在多线程下的共享与释放问题

    最近被多线程给坑了下,没意识到类变量在多线程下是共享的,还有一个就是没意识到 内存释放问题,导致越累越大 1.python 类变量 在多线程情况 下的 是共享的 2.python 类变量 在多线程情况 ...

  3. python中几个常见的黑盒子之“字典dict” 与 “集合set”

    这里说到"字典dict" 和 "集合set"类型,首先,先了解一下,对于python来说,标准散列机制是有hash函数提供的,对于调用一个__hash__方法: ...

  4. xcode8 上传ipa文件无法构建版本

    在xcode8 升级后上传ipa文件 需要设置一个安全提示,现在上传app store的方式为xcode或者 application loader 一.xcode 准备工作完成后点击Product-- ...

  5. SQL查询符合条件的记录的总数

    1. select count(*) from table;    //统计元组个数 2. select count(列名) from table;  //统计一列中值的个数 3. select co ...

  6. UITextField使用详解

    转iOS中UITextField使用详解 (1) //初始化textfield并设置位置及大小   UITextField *text = [[UITextField alloc]initWithFr ...

  7. PHP那些非常有用却鲜有人知的函数

    PHP里有非常丰富的内置函数,很多我们都用过,但仍有很多的函数我们大部分人都不熟悉,可它们却十分的有用.这篇文章里,我列举了一些鲜为人知但会让你眼睛一亮的PHP函数. levenshtein() 你有 ...

  8. nexus2.1.2的配置

    最近在学习maven,逐渐接触到私服的搭建,也就着手学习使用nexus了,在http://www.sonatype.org/nexus/go网站上nexus最新版本的是,不过版本要同jvm的版本匹对, ...

  9. iTunes Connect 开发者上手经验(转)

    原文:http://www.cnblogs.com/zhw511006/archive/2013/01/15/2860945.html iOS Developer通常需要用到 developer.ap ...

  10. Scrum - BB项目日志

    这是第三个Scrum团队开发,也是我首次担任Scrum Master一职.所以需要掌握的流程还是很多,也会碰到各种问题,在此记录一下,希望对以后有所帮助. Day1: 开了一次kick-off mee ...