本人没有学过面向对象的语言,对于模块化开发,也是基于对前端项目制作时的经验、阅读其他人的文章以及对javascript学习的,因此本文仅为记录本人目前所感受到的问题,如有错误,恳请指正。

什么是模块化

模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。模块化用来分割,组织和打包软件。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体,完成整个系统所要求的功能。

也就是说,分析一个复杂的问题,将其拆分成一系列子问题,然后再组合再一起。引用叶小钗老师的例子理解requireJS-实现一个简单的模块加载器

模块化的简单实例

到手的薪水与绩效、公积金、个人所得税、基本工资有关。最开始,我可能会这么写:

1
2
3
4
5
6
7
8
9
var mySalary,
salary = 1000,
performanceCoefficient = 0.2,
companyReserve = salary * 0.2,
incomeTax = salary * 0.2;
mySalary = salary + salary * performanceCoefficient;
mySalary = mySalary - companyReserve - incomeTax;

看起来很不错,是么?但是,绩效、公积金、税的计算不会那么简单,于是乎,以个人所得税为例我做个修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var mySalary,
salary = 1000,
performanceCoefficient = 0.2,
companyReserve = salary * 0.2,
incomeTax;
var incomeTaxBase = salary - 3500;
if(incomeTaxBase <= 0){
incomeTax =0;
}else if(incomeTaxBase > 0 && incomeTaxBase <= 1500){
incomeTax = 0.03 * incomeTaxBase;
}else if(incomeTaxBase >1500 && incomeTaxBase <= 4500){
incomeTax = 0.03 * 1500 + 0.1 * (incomeTaxBase - 1500);
}else if{}//以后省略
mySalary = salary + salary * performanceCoefficient;
mySalary = mySalary - companyReserve - incomeTax;

可以发现,整个程序变得十分复杂,若哪里计算有问题,都不方便检查。而这仅仅是改变了个人所得税收入的计算。(当然这个计算公式也有问题)

因此,我们会将不同的计算封装到函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//求得绩效系数
var performanceCoefficient = function () {
return 0.2;
};
//住房公积金计算方式
var companyReserve = function (salary) {
return salary * 0.2;
};
//个人所得税
var incomeTax = function (salary) {
return salary * 0.2;
};
//基本工资
var salary = 1000;
//最终工资
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));

这样,当我们需要计算工资的时候,只需要调用不同的函数,就能得到所需要的值,而不用考虑其中的细节。如若某种参数的计算过程发生改变,也只需要更改函数中的内容,而函数的调用方法不会改变。

以上的方法,有个很明显的不足,就是对全局的污染。考虑到javascript中,函数拥有自己的作用域,我们可以通过以下方式进行封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var module1 = (function(){
//求得绩效系数
var performanceCoefficient = function () {
return 0.2;
};
//住房公积金计算方式
var companyReserve = function (salary) {
return salary * 0.2;
};
//个人所得税
var incomeTax = function (salary) {
return salary * 0.2;
};
//基本工资
var salary = 1000;
//最终工资
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
return {
getSalary:function(salary){
//最终工资
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
return mySalary;
}
};
})();
module1.getSalary(1000);

我这样的写法,忽略了可重用性(工资各参数的计算仅能在此模块中使用,无法在其他模块中使用),且当参数计算过于复杂,整个模块会特别大。因此,比较好的方式是将各参数的计算分离到不同的javascript文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script src="companyReserve.js" type="text/javascript"></script>
<script src="incomeTax.js" type="text/javascript"></script>
<script src="performanceCoefficient.js" type="text/javascript"></script>
<script type="text/javascript">
//基本工资
var salary = 1000;
//最终工资
var mySalary = salary + salary * performanceCoefficient();
mySalary = mySalary - companyReserve(mySalary) - incomeTax(mySalary - companyReserve(mySalary));
console.log(mySalary);
</script>

这种方式,要求我们按照正确顺序引入各文件。正如叶小钗老师所说,这种方式并不是按照模块划分,而是按照文件划分。我们需要一种方式,来处理各模块的引入,依赖问题。

RequireJS

RequireJS 是一个JavaScript模块加载器。它非常适合在浏览器中使用, 它非常适合在浏览器中使用,但它也可以用在其他脚本环境, 就像 Rhino and Node. 使用RequireJS加载模块化脚本将提高代码的加载速度和质量。

RequireJS遵循AMD规范。

引入模块

首先,在html文件中引入requireJS,并指定入口函数。

1
<script src="scripts/require.js" data-main="scripts/main.js"></script>

其中main.js的路径相对于html的路径。

配置requireJS

main.js中,需要对requireJS进行配置,如基本路径、模块路径映射等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
requirejs.config({
baseUrl:'./',//默认此路径相对于引入requireJS的html文件。以后其余模块的路径,都将相对于此路径。
//定义各模块的映射
paths: {
jquery: 'lib/jquery',
validate: 'app/validate'
},
//非AMD模块,在此声明依赖关系、设置模块的"浏览器全局变量注入"型脚本做依赖和导出配置。
//Remember: only use shim config for non-AMD scripts,
//scripts that do not already call define(). The shim
//config will not work correctly if used on AMD scripts,
//in particular, the exports and init config will not
//be triggered, and the deps config will be confusing
//for those cases.
shim: {
'backbone': {
//These script dependencies should be loaded before loading
//backbone.js
deps: ['underscore', 'jquery'],
//Once loaded, use the global 'Backbone' as the
//module value.
exports: 'Backbone'
},
'underscore': {
exports: '_'
},
'foo': {
deps: ['bar'],
exports: 'Foo',
init: function (bar) {
//Using a function allows you to call noConflict for
//libraries that support it, and do other cleanup.
//However, plugins for those libraries may still want
//a global. "this" for the function will be the global
//object. The dependencies will be passed in as
//function arguments. If this function returns a value,
//then that value is used as the module export value
//instead of the object found via the 'exports' string.
//Note: jQuery registers as an AMD module via define(),
//so this will not work for jQuery. See notes section
//below for an approach for jQuery.
return this.Foo.noConflict();
}
}
});
// 主函数逻辑
requirejs(['jquery', 'canvas', 'app/sub'],function($,canvas, sub) {
//jQuery, canvas and the app/sub module are all
//loaded and can be used here now.
});

定义模块

模块定义的格式为:define(模块名,依赖,回调函数);

1
2
3
define("foo/title",["my/cart", "my/inventory"],function(cart, inventory) {
//Define foo/title object in here.
});

CommonJS

服务器端的模块化编程,遵循commonJS规范。它不适合浏览器端,因为它加载模块是同步的,会阻塞页面。

使用

1
2
3
var math = require('math');
math.add(2,3);

定义

1
2
3
var moduleName = {};
module.exports = moduleName;

编写兼容各标准的插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(function (root) {
var moduleName = {};
if (typeof define === 'function' && define.amd) {
// AMD
define([], function () {
return moduleName;
});
} else if (typeof exports === 'object') {
// Node.js
module.exports = moduleName;
} else {
// Global variable
root.moduleName = moduleName;
}
})(this);

参考

理解requireJS-实现一个简单的模块加载器

Javascript模块化编程(一):模块的写法

RequireJS