前言
希望编写程序能像玩积木一样,首先规划要产出怎样的作品,然后在积木堆中挑选合适的积木块,最后一组合就完工了。
于是JavaScript需要类似这样模块化,每个模块都隐藏内部细节并且对外暴露接口,再处理好模块之间的依赖关系,就可以达到玩积木的效果了。
虽然现有很多模块化框架/工具,但对于新手来说,为什么不自己撸一个简单的模块化工具呢?
希望通过这个工具把自己觉得好用的代码以模块的方式组织起来,渐渐形成自己的JS库,之后可以勇敢地和HR说,自己的小项目用的是自己小JS库,^_^。我觉得,在这个封装的过程中,新手能学习到很多东西。
新手嘛,多造轮子总是有好处的,=_=。
从闭包到模块
以下是《你所不知道的JavaScript(上卷)》中对于闭包的说明。
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。
其实,不管怎样,闭包正如其字面意思一样,既能提供一个相对封闭的空间,也能向外界暴露必要的接口。这不就正符合我们模块化的需求吗?
在此,建议参考这篇文章,以加强您对闭包的理解:《假如技术HR问您JavaScript的“闭包”,嘿嘿嘿,举这个例子就够了》。
最简单的模块
var test = (function test(){
function run(){
console.log("run test");
}
return {
run: run
};
})();
test.run();
上面的代码有个叫test的函数作为模块创建器,每次调用它都可以创建一个新的模块。这里使用立即执行函数,立即创建了一个test模块。参考闭包的概念,外部可以调用test模块中的run函数,同时test模块又有自己独立的作用域。能达到一个积木块(模块)的要求。
简单的模块这样写没有问题,但是模块间的依赖问题没有解决。
最简单的模块管理工具
模块之间必然会存在依赖关系,而模块管理工具需要能够很好地管理模块间的依赖。下面我们模仿实现了AMD规范的工具requirejs,主要是模仿其define,get的API风格,自己写一个简单的版本。
//模块管理工具,MyModules
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i=0; i<deps.length; i++){
//将依赖的名字替换成已经注册了的模块
deps[i] = modules[deps[i]];
}
//将依赖数组展开成参数传入模块的构建函数,生成新模块
modules[name] = impl.apply(impl, deps);
}
function get(name){
return modules[name];
}
return {
define: define,
get: get
}
})();
//定义一个模块,data
MyModules.define("data",[],function(){
var name = "miku";
function getName(){
return name;
}
return {
getName:getName
}
});
//定义一个模块,app
//该模块依赖data模块
MyModules.define("app", ["data"], function(data){
function run(){
console.log(data.getName());
}
return {
run:run
}
});
//取出app模块
var app = MyModules.get("app");
//调用app模块的run方法
app.run();
可以看出,利用MyModules可以很方便地定义使用模块,管理模块依赖。但是还存在一个问题,MyModules对于模块定义的顺序有要求。以上面的例子来说,就是app模块依赖data模块,那data模块必须在app模块之前被定义。这个限制让我们实际使用中不是很方便。接下来我们将改进它。
改进模块管理工具
我们需要让模块管理工具不需要限制模块的定义顺序,这里我的做法是,使用一个rebuilds数组来保存未成功构建的模块。每次有新模块构建成功的时候就会重新尝试去构建整个rebuilds数组中的模块。具体看下面的代码。
window.mm_modules = (function Manager() {
var debug = false;
var modules = {};
var rebuilds = [];
function copyArray (array){
var tempArray = [];
for(var i=0; i<array.length; i++){
tempArray.push(array[i]);
}
return tempArray;
}
function define(name, deps, impl) {
//判断依赖构建是否成功
var depsDone = true;
//拷贝一份当前想要构建的模块,当构建失败时使用
var rebuildCopy = {
name : name,
deps : copyArray(deps),
impl : impl
};
//循环依赖数组,构建依赖
for (var i=0; i<deps.length; i++){
//将依赖的名字替换成已经注册了的模块
deps[i] = modules[deps[i]];
//有依赖的模块未定义,所以这个模块构建失败
if(!deps[i]){
depsDone = false;
break;
}
}
//如果依赖构建成功,即模块构建成功
if(depsDone){
//将依赖数组展开成参数传入模块的构建函数,生成新模块
modules[name] = impl.apply(impl, deps);
//从rebuilds数组中移除
if(rebuilds[name]){
delete rebuilds[name];
}
//循环rebuilds数组,尝试从新构建之前构建失败的模块
for (key in rebuilds){
var rebuild = rebuilds[key];
if(rebuild){
//递归调用
define(rebuild.name, rebuild.deps, rebuild.impl);
}
}
}
//模块构建失败,存入rebuilds数组中,等待下一次重新构建
else{
rebuilds[name] = rebuildCopy;
}
if(debug){
console.log("[mm_modules debug]need rebuild modules:", rebuilds);
}
}
function get(name){
return modules[name];
}
return {
define: define,
get: get
}
})();
改进后的模块管理工具,能够自动地处理模块依赖,而不需要限制定义顺序了。
那,能不能更进一步呢?试着想一下,我们日常会怎么使用?单文件单模块,然后把这些文件放在不同文件夹里组织好。于是,我就想到使用gulp这样的工具辅助我们。
gulp辅助
请参考下面的目录结构。
├── dist
│ ├── index.html
│ └── js
│ └── mm-modules-build.js
├── gulpfile.js
├── mm-modules
│ ├── queryObject.js
│ ├── request.js
│ ├── template.js
│ ├── test.js
│ └── util.js
├── mm-modules.js
可以在mm-modules下随意地定义模块,如util模块内有各种工具函数,template模块则包含了artTemplate模版引擎。之后利用gulp将mm-modules.js(模块管理工具)与mm-modules下所有的模块文件打包成mm-modules-build.js。项目中只要引入mm-modules-build.js即可。
结尾
到此,我们自己构建了一个很实用的JavaScript模块化工具,项目的源码在这里:https://github.com/MIKUScallion/mm-modules,喜欢的话,给个✨。
再回顾一下前言的话。
希望通过这个工具把自己觉得好用的代码以模块的方式组织起来,渐渐形成自己的JS库,之后可以勇敢地和HR说,自己的小项目用的是自己小JS库,^_^。我觉得,在这个封装的过程中,新手能学习到很多东西。
新手嘛,多造轮子总是有好处的,=_=。
参考
《你所不知道的JavaScript(上卷)》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。