模块化开发(一)
先知先觉
随着前端项目越来越复杂,使用模块化开发可以提高代码的重用性,并且使项目结构清晰化。一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范,否则就都乱套了。
目前,通行的js模块规范主要由三种:AMD
,CMD
,CommonJS
.
简单来说,就是使用define
定义模块,使用require
调用模块。
先盗用一张图来方便理解他们的异同:
AMD规范
AMD规范文档AMD
即Asynchronous Module Definition
,中文名是异步模块定义
的意思。它是一个在浏览器端模块化开发的规范,服务器端的规范是CommonJS
模块将被异步加载
,模块加载不影响后面语句的运行。所有依赖某些模块的语句均放置在回调函数中。
AMD
是RequireJS
在推广过程中对模块定义的规范化的产出。
优点:
- 实现js文件的异步加载,避免网页失去响应;
- 管理模块之间的依赖性,便于代码的编写和维护。
目前,实现AMD的库有RequireJS
、curl
、Dojo
、Nodules
等。
define() 函数
AMD规范只定义了一个函数 define,它是全局变量
。函数的描述为:1
define(id?, dependencies?, factory);
id:指定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。
依赖参数是可选的,如果忽略此参数,它应该默认为[“require”, “exports”, “module”]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。工厂方法factory,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
(来自-segment)
模块名用来唯一标识
定义中模块,它们同样在依赖性数组中使用,当一个文件中有多个define定义时,需要使用文件路径作为模块名来区分调用的模块是哪个。
模块名可以为 “相对的” 或 “顶级的”。如果首字符为“.”或“..”则为相对的模块名;如果你定义了根目录(baseUrl
),那么顶级的模块名从根命名空间的概念模块解析.
函数定义例子:创建一个名为”alpha”的模块,使用了require,exports,和名为”beta”的模块:1
2
3
4
5
6
7define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
RequireJS默认假定所有的依赖资源都是js脚本,因此无需在module ID上再加”.js”后缀,RequireJS在进行module ID到path的解析时会自动补上后缀。
require() 函数
可以看下面的实际例子:1
2
3require(['jquery','underscore','backbone'],function($, _, Backbone){
console.log($(".box").width());
});
require()函数接受两个参数。第一个参数是一个数组
,表示所依赖的模块,上例就是['jquery', 'underscore', 'backbone']
,即主模块依赖这三个模块;第二个参数是一个回调函数
,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。require()异步加载jquery
,underscore
和backbone
,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
RequireJS模块的加载(手打RequireJS相关代码地址)
调用require.js
1 | <script src="js/require.js" data-main="js/main"></script> |
data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
模块加载自定义
使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。
如果这些模块在其他目录,比如js/lib目录,则有两种写法。一种是逐一指定路径。1
2
3
4
5
6
7require.config({
paths: {
"jquery": "lib/jquery.min",
"underscore": "lib/underscore.min",
"backbone": "lib/backbone.min"
}
});
另一种则是直接改变基目录(baseUrl)。1
2
3
4
5
6
7
8require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});
注意一点,如果加载require.js模块的时候出现timeout
现象,解决办法:只要在config里面加上1
waitSeconds: 0, //解决timeout问题
除了paths,还有shim用来加载非规范的模块,可以查看相关信息.
AMD模块的写法
require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。
具体来说,就是模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。
假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:1
2
3
4
5
6
7
8
9// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
加载方法如下:1
2
3
4// main.js
require(['math'], function (math){
alert(math.add(1,1));
});
如果这个模块被其他模块依赖,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。1
2
3
4//myLib.js
define(['../module/math'],function(math){
return math.add(1,1);
})
当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。
先写到这吧,有点累了,下次接着写。
参考文章:
1.https://github.com/amdjs/amdjs-api/wiki/AMD-(%E4%B8%AD%E6%96%87%E7%89%88)