Js的模块化详解(一)
前言—我们为什么应该写模块化的代码
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 | //math.js |
1 | //caculator.js |
这里我们发现两个问题:
- 几乎没有封装性,这些变量和函数都散落在全局。
- 它的接口结构并不明显,我们并不知道这个math到底输出了哪些接口
这时,我们可以引入IIFE(立即调用函数表达式)来实现。
IIFE
我们先看第一种写法
1 | //caculator-1.js |
这样呢这个compute方法可以读取到这个action,同时又不会暴露到闭包外面,这是一个优势(访问控制),但它依然没有依赖声明。
1 | //caculator-1.js |
如上,把math模块当作参数传进去。我们可以对形参进行命名,这样的话依赖关系就非常明显了,这种写法相对于第一种可以说是非常高明了。但它仍存在两个问题:
- 依然污染了全局变量
- 必须手动进行依赖管理,而且写法繁琐
命名空间
命名空间可以帮助我们只暴露一个全局变量就实现所有模块的声明:
1 | //math.js |
在这里,namespace传入三个参数:math模块声明;依赖(此处为空);模块的构成
1 | //caculator.js |
可以看注释部分,一目了然,依赖声明和依赖注入都很清晰
那么这个namespace到底是个什么东西,我们应该怎么写呢?其实也很简单:
1 | var namespace = (function() { |
这其实与AMD规范的实现已经非常接近了,在下一篇文章中将会详细讲解AMD。
但它依然没有解决依赖管理的问题:如果我们的模块分散在不同的文件里面,我们就需要对脚本文件的顺序进行自己手动的排序。这在小型页面里当然没有问题,但是——
如果是这样呢?此时脑中是这样的
绝对会抓狂的!
那么应该怎么办呢?下一篇将会讲解常用现代的模块化开发模式

