由于JavaScript开发变得越来越普遍,命名空间和依赖管理更加难以处理,前赴后继的程序员们提出来很多的解决方案,本文将探讨一些经典的方案,并描述这些方案解决了哪些问题。
为什么需要模块系统
作为开发人员,我们一定知道封装和依赖。在实际的项目开发中,我们通常会引入项目依赖,如果没有封装机制,这可能会导致代码间的各种冲突,所以我们在看一些C语言的源码库的时候,经常会看到各种前缀:
#ifndef MYLIB_INIT_H
#define MYLIB_INIT_H
enum mylib_init_code {
mylib_init_code_success,
mylib_init_code_error
};
enum mylib_init_code mylib_init(void);
//(...)
#endif //MYLIB_INIT_H
封装可以有效解决代码冲突。但在Web端的js开发中,仅仅做到封装是不够的,我们还要确保各依赖模块能按照正确的顺序加载和执行。
我们通过Backbone.js的一个例子来说明手动控制依赖的加载顺序:
<html lang="en">
<head>
<meta charset="utf-8">
<title>Backbone.js Todos</title>
<link rel="stylesheet" href="todos.css"/>
</head>
<body>
<script src="../../test/vendor/json2.js"></script>
<script src="../../test/vendor/jquery.js"></script>
<script src="../../test/vendor/underscore.js"></script>
<script src="../../backbone.js"></script>
<script src="../backbone.localStorage.js"></script>
<script src="todos.js"></script>
</body>
<!-- (...) -->
</html>
随着现代JavaScript的开发越来越复杂,依赖管理也变得越来越困难,引入模块系统是大势所趋。
特殊的模块封装
在介绍现代模块系统之前,我们先介绍一种特殊的编程模式,在一定程度上它也可以解决很多JavaScript应用的模块管理。
var myRevealingModule = (function () {
var privateVar = "Ben Cherry",
publicVar = "Hey there!";
function privateFunction() {
console.log( "Name:" + privateVar );
}
function publicSetName( strName ) {
privateVar = strName;
}
function publicGetName() {
privateFunction();
}
// Reveal public pointers to
// private functions and properties
return {
setName: publicSetName,
greeting: publicVar,
getName: publicGetName
};
})();
myRevealingModule.setName( "Paul Kinlan" );
这是通过函数作用域来实现私有变量的封装,有函数return语句暴露对外访问的接口。在上面的例子中,函数内部不一定需要通过var来声明变量,函数也无需立即执行,命名函数也可以作为一个模块来使用。
在很长一段时间,这种方式可以很好地实现模块封装,但它不能解决依赖管理的问题,同时也无法引入其它模块,除非在函数内容通过eval执行外部JavaScript。
特点
- 实现简单,原生支持;
- 在单个文件中定义多个模块。
缺陷
- 无法导入其他文件中的模块;
- 手动处理依赖管理;
- 无法实现依赖的异步加载;
- 无法有效处理依赖间的循环引用;
- 无法静态分析源码依赖。