文章目录
  1. 1. 前言—我们为什么应该写模块化的代码
  2. 2. Js中的模块化思路
    1. 2.1. IIFE
    2. 2.2. 命名空间

前言—我们为什么应该写模块化的代码

The secret to building large apps is never build large apps. Break your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application.
——Justin Meyer

这句话是从推特上摘录下来的,大意就是创建一个大型的app,不是去写很多代码,而是将你的应用程序打碎到很小的模块,然后呢再组装它们,这句话非常有道理。

如果我们想造一辆汽车,应该怎么做呢?

你一开始看得出这些零件是干什么的吗?它会组装成一个发动机。

发动机有很多零件组成,有些零件是有标称的,它就相当于一个内部依赖的模块;

但有些并不是标准的,只是这个发动机才会用到的零件,这些零件就相当于一些内部的成员,这些内部成员就不需要组装成一个特有模块。

再反过来说,发动机又是组成汽车的一部分,汽车对发动机内部使用的什么零件,他是一点都不关心的,他关心的只是发动机的动力输出。

Js中的模块化思路

通常,程序的模块化由以下部分组成:

  • 封装实现
  • 暴露接口
  • 声明依赖

我们先来看一下,如果我们要写一个计算器,没有模块系统的情况下代码怎样组织

1
2
3
4
5
6
7
//math.js
function add(a,b) {
return a+b;
}
function sub(a,b) {
return a-b;
}
1
2
3
4
5
6
7
8
9
//caculator.js
var action = "add";

function compute(a,b) {
switch (action) {
case "add": return add(a,b)
case "sub": return sub(a,b)
}
}

这里我们发现两个问题:

  1. 几乎没有封装性,这些变量和函数都散落在全局。
  2. 它的接口结构并不明显,我们并不知道这个math到底输出了哪些接口

这时,我们可以引入IIFE(立即调用函数表达式)来实现。

IIFE

我们先看第一种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
//caculator-1.js
var caculator = (function() {
var action = "add";

return{
compute: function(a,b) {
switch (action) {
case "add": return math.add(a,b)
case "sub": return math.sub(a,b)
}
}
}
})()

这样呢这个compute方法可以读取到这个action,同时又不会暴露到闭包外面,这是一个优势(访问控制),但它依然没有依赖声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//caculator-1.js
var caculator = (function(m) {
var action = "add";

function compute(a,b) {
switch (action) {
case "add": return m.add(a,b)
case "sub": return m.sub(a,b)
}
}
return{
compute: compute
}
})(math)

如上,把math模块当作参数传进去。我们可以对形参进行命名,这样的话依赖关系就非常明显了,这种写法相对于第一种可以说是非常高明了。但它仍存在两个问题:

  1. 依然污染了全局变量
  2. 必须手动进行依赖管理,而且写法繁琐

命名空间

命名空间可以帮助我们只暴露一个全局变量就实现所有模块的声明:

1
2
3
4
5
6
7
8
9
10
//math.js
namespace("math",[],function() {
function add(a,b) { return a+b; }
function sub(a,b) { return a-b; }

return {
add: add,
sub: sub
}
})

在这里,namespace传入三个参数:math模块声明;依赖(此处为空);模块的构成

1
2
3
4
5
6
7
8
9
10
11
12
//caculator.js
namespace("caculator",["math"],function(m) {
// 依赖声明 依赖注入
var action = "add";
function compute(a,b) {
return m[action](a,b)
}
//...
return {
compute: compute
}
})

可以看注释部分,一目了然,依赖声明和依赖注入都很清晰

那么这个namespace到底是个什么东西,我们应该怎么写呢?其实也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var namespace = (function() {
var cache = {} //缓存所有模块
function createModule(name,deps,definition) {
//三个参数:模块名,依赖列表,定义↑
if(arguments.length ===1) return cache[name]

//必须先取得所有依赖的模块
deps = deps.map(function(depName) {
return ns(depName)
})

//初始化模块并返回
cache[name] = definition.apply(null,deps)

return cache[name];

}
return createModule
})()

这其实与AMD规范的实现已经非常接近了,在下一篇文章中将会详细讲解AMD。

但它依然没有解决依赖管理的问题:如果我们的模块分散在不同的文件里面,我们就需要对脚本文件的顺序进行自己手动的排序。这在小型页面里当然没有问题,但是——
如果是这样呢?
如果是这样呢?此时脑中是这样的


绝对会抓狂的!

那么应该怎么办呢?下一篇将会讲解常用现代的模块化开发模式

文章目录
  1. 1. 前言—我们为什么应该写模块化的代码
  2. 2. Js中的模块化思路
    1. 2.1. IIFE
    2. 2.2. 命名空间