最近大半年也写了不少通用模块,却从来没使用过一种通过书写格式。
目前公司的前端通用库还是jQuery
, 但也不是所有的模块都适合以jQuery 插件
的形式开发。平时写的时候一般以立即执行函数或 OO 形式开发,以目前项目情况来看也不是很需要用require.js
或sea.js
之类的加载器。
其实我的需求很简单,就是抹去模块在全局作用域的定义,以通用的形式定义和引用。
最终目标
从结果出发分析需求,先列出来最终想要的代码形式。
module(function (exports) { console.log(require('m1').msg); }); module('m1', function (exports) { exports.msg = 'hello'; }); module('m2', ['m1'], function (m1, exports) { exports.msg = m1.msg + "World"; }); module(['m2'], function (m2, exports) { console.log(m2.msg); });
|
使用module
定义一个模块,拥有一个函数上下文,可以定义模块名称和引用其他模块,也可以通过注入的exports
参数输出模块开放接口。
某些时候也许需要require('模块名')
这种形式来直接引用模块。
1. 注入环境变量
从以上四种模块形式来看,module
和require
两个变量的作用域是全局,需要注入到window
中。
(function(w, f){ w.module = function () { } w.require = function (namne) { } }(window, undefined));
|
一个简单的立即执行函数就可以做到。
2. 解析 module 参数
module
参数的数量是不固定的,最少1个(形式1)、最多3个(形式3)。
可以需要通过判断参数数量和类型加以区分。
w.module = function () { var modName = arguments[0], mods = arguments[1], context = arguments[arguments.length - 1]; if(modName instanceof Array) { mods = modName; modName = f; } }
|
参数数量不固定,也就不必定义形参了,直接从arguments
中获取。
arguments[0]
是模块名,模块名存在时永远是参数第一位;
arguments[1]
是引用模块,模块名不存在时arguments[0]
为引用模块,所以判断模块名是数组类型时重新赋值;
不管何种形式模块函数都是最后一个参数,直接使用arguments[arguments.length - 1]
获取;
3. 实现 module
模块函数的参数数量也是不固定的,但至少需要注入exports
来输出开放接口。
每多引用一个模块,就需要向模块函数中注入一个对应参数。
var modules = {}; w.module = function () { var modName = arguments[0], mods = arguments[1], context = arguments[arguments.length - 1]; if(modName instanceof Array) { mods = modName; modName = f; } var args = []; if(mods instanceof Array) { for(var i = 0; i < mods.length; i++) { args.push(modules[mods[i]]); } } if(typeof modName === 'string') { modules[modName] = {}; args.push(modules[modName]); } context.apply(this, args); }
|
遍历mods
取出模块:定义了一个变量modules
存放模块,那么假如一个模块引用了模块a
、b
,则mods = ['a', 'b'];
,a
、b
两个模块的真值即为modules['a']
、modules['b']
。当模块为形式2时,参数有两个,参数2为模块函数。此时 arguments[1] === arguments[arguments.length - 1]
,引用模块(mods)也指向了模块函数,所以要判断mods
类型。
注册模块:每一个具名模块都需要注册到modules
中,以备其他模块引用。当模块为匿名模块时,不需要注册,所以要判断modName
类型。
注入参数,执行模块:对于不定数量的参数注入,显然是使用apply
最简单。
4. 实现 require
var modules = {}; w.module = function () { } w.require = function (namne) { return modules[namne]; }
|
暂时不考虑异步加载、加载路径解析等各种情况了,直接从容器中取到模块…
5. 完整代码
(function(w, f){ var modules = {}; w.module = function () { var modName = arguments[0], mods = arguments[1], context = arguments[arguments.length - 1]; if(modName instanceof Array) { mods = modName; modName = f; } var args = []; if(mods instanceof Array) { for(var i = 0; i < mods.length; i++) { args.push(modules[mods[i]]); } } if(typeof modName === 'string') { modules[modName] = {}; args.push(modules[modName]); } context.apply(this, args); } w.require = function (namne) { return modules[namne]; } }(window, undefined)); module(function (exports) { console.log(require('m1').msg); }); module('m1', function (exports) { exports.msg = 'hello'; }); module('m2', ['m1'], function (m1, exports) { exports.msg = m1.msg + "World"; }); module(['m2'], function (m2, exports) { console.log(m2.msg); });
|
执行以上代码会报错,代码是自上而下同步执行的,形式1运行阶段模块m1
还未定义。
把形式1放到形式4之后执行,输出: