类似于 Java 的 package/import,在 JavaScript 中也有类似的 define/require,它用来异步加载 module 化的 js,从而提高运行效率。
模块化加载的必要性,起源于 nodejs 的出现。但是 JavaScript 并没有内置模块系统,所以就出现了很多规范。 主要有2种:CommonJS 和 AMD(Asynchronous Module Definition)。还有国内兴起的 CMD(Common Module Definition)
CommonJS 主要面对的是服务器,代表是 Node.js;AMD 针对浏览器进行了优化,主要实现 require.js;CMD 是seajs。
cordova-js 最开始采用的是 require.js 作者写的 almond.js(兼容 AMD 和 CommonJS),但之后由于特殊需求(比如模块不存在的时候要 throw 异常),最终从 almond.js fork 过来实现了一个简易 CommonJS 风格的模块系统,同时提供了和 nodejs 之间很好的交互。在 cordova.js 中可以直接使用 define()和 require(),在其他文件可以通过 cordova.define()和 cordova.require()来调用。所以 src/scripts/require.js 中定义的就是一个精简的 JavaScript 模块系统。
源码如下:
Js 代码
// file: src/scripts/require.js // 定义2个cordova.js内部使用的全局函数require/define var require, define; // 通过自调用的匿名函数来实例化全局函数require/define (function () { // 全部模块 var modules = {}, // 正在build中的模块ID的栈 requireStack = [], // 标示正在build中模块ID的Map inProgressModules = {}, SEPARATOR = "."; // 模块build function build(module) { // 备份工厂方法 var factory = module.factory, // 对require对象进行特殊处理 localRequire = function (id) { var resultantId = id; if (id.charAt(0) === ".") { resultantId = module.id.slice(0, module.id.lastIndexOf(SEPARATOR)) + SEPARATOR + id.slice(2); } return require(resultantId); }; // 给模块定义一个空的exports对象,防止工厂类方法中的空引用 module.exports = {}; // 删除工厂方法 delete module.factory; // 调用备份的工厂方法(参数必须是require,exports,module) factory(localRequire, module.exports, module); // 返回工厂方法中实现的module.exports对象 return module.exports; } // 加载模块 require = function (id) { // 如果模块不存在抛出异常 if (!modules[id]) { throw "module " + id + " not found"; // 如果模块正在build中抛出异常 } else if (id in inProgressModules) { var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id; throw "Cycle in require graph: " + cycle; } // 如果模块存在工厂方法说明还未进行build(require嵌套) if (modules[id].factory) { try { // 标示该模块正在build inProgressModules[id] = requireStack.length; // 将该模块压入请求栈 requireStack.push(id); // 模块build,成功后返回module.exports return build(modules[id]); } finally { // build完成后删除当前请求 delete inProgressModules[id]; requireStack.pop(); } } // build完的模块直接返回module.exports return modules[id].exports; }; // 定义模块 define = function (id, factory) { // 如果已经存在抛出异常 if (modules[id]) { throw "module " + id + " already defined"; } // 模块以ID为索引包含ID和工厂方法 modules[id] = { id: id, factory: factory }; }; // 移除模块 define.remove = function (id) { delete modules[id]; }; // 返回所有模块 define.moduleMap = modules; })(); // 如果处于nodejs环境的话,把require/define暴露给外部 if (typeof module === "object" && typeof require === "function") { module.exports.require = require; module.exports.define = define; }
其中 factory(localRequire, module.exports, module);
第一个参数“localRequire”实质还是调用全局的 require()函数,只是把 ID 稍微加工了一下支持相对路径。cordova.js 没有用到相对路径的 require,但在一些 Plugin 的 js 中有,比如 Contact.js 中 ContactError = require('./ContactError');
不知道什么原因要把 module.exports 单做为工厂方法的第两个参数,cordova.js 中除了个别地方用到第二个参数(base64.js、uilder.js、modulemapper.js、pluginloader.js、utils.js),大部分都是在用第三个参数在工厂方法里调用 module.exports。Plugin 的 js 代码中也基本是 module.exports。