模块化开发
作者:互联网
模块化开发
一、JavaScript模块化的必要性
随着网站逐渐变成“互联网应用程序(WebApp)”,嵌入网页的JavaScript代码越来越庞大,越来越复杂。
网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等…开发者不得不使用软件工程的方法,管理网页的业务逻辑。
Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
但是,Javascript不是一种模块化编程语言,它不支持"类"(class),更遑论"模块"(module)了。(正在制定中的ECMAScript标准第六版,将正式支持"类"和"模块",但还需要很长时间才能投入实用。)
Javascript社区做了很多努力,在现有的运行环境中,实现"模块"的效果。
二、Javascript模块化的写法
1.原始写法
模块就是实现特定功能的一组方法
只要吧不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
比如:tool.js
function m1(){
//...
}
function m2(){
//...
}
上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。
这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
2.对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
_count:0,
m1:function(){
//...
},
m2:function(){
//...
}
});
//上面的函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。
module1.m1();
//【注】但是这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
3.立即执行函数写法(闭包)
使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1:m1,
m2:m2
}
})();
//使用上面写法,外部代码无法读取内部_count变量
console.log(module1._count);
4.放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。
//接上面代码
var module1 = (function(mod){
mod.m3 = function(){
//...
}
return mod;
})(module1);
//上述代码在module1模块添加了一个新方法m3(),然后返回新的moudule1模块。
5.宽放大模式
在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"。
var module1 = (function(mod){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
mod.out1 = m1;
mod.out2 = m2;
return mod;
})(module1 || {});
var module1 = (function(mod){
mod.m3 = function(){
//...
}
return mod;
})(module1 || {});
三、模块规范
目前,通行的Javascript模块规范共有两种:CommonJS和AMD(CMD)。
2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。
这标志"Javascript模块化编程"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。
node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。
因为这个系列主要针对浏览器编程,不涉及node.js,所以对CommonJS就不多做介绍了。我们在这里只要知道,require()用于加载模块就行了。
CommonJS规范(服务器)编写代码
声明:
module.exports = {
outA:showA,
outB:showB
}
引入:(同步执行)
var moduleA = require('moduleA.js');
moduleA.outA();
moduleA.outB();
AMD规范:(客户端/浏览器)
声明:
define(function(){
//代码
return{
outA:showoA,
outB:showB
}
})
引入:(异步执行)
require("moduleA.js",function(moduleA){
//这里的代码。模块引入之后执行。
moduleA.putA();
moduleA.outB();
})
alert("Hello World");
EMAC6(模块化规范)
声明:
export = {
outA:showA,
outB:showB
}
引入:
import moduleA from "moduleA.js"
moduleA.outA()
moduleA.outB();
require.js用法
require.js的诞生就是为了解决这两个问题:
- 实现js文件的异步加载,避免网页失去响应
- 管理模块之间的依赖性,便于代码的编写和维护
require.js的加载
使用require.js的第一步就是去官方下载最新版本的require.js。下载后把它放在js的子目录下然后进行加载
<script src="js/require.js" async = 'true' defer data-main="js/main"></script>
==注:==async属性是表明这个文件是否需要异步加载,避免网页失去响应。IE不支持这个属性,支持defer,所以吧defer也写上
加载完require.js以后,下一步就要加载我们自己的代码。假定我们自己的代码文件是main.js,也放在js目录下面。那么,只要像上面那么写就行了。
data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以吧main.js简写成main。
主模块的写法
上一节的main.js,我把它称为"主模块",意思是整个网页的入口代码。它有点像C语言的main()函数,所有代码都从这儿开始运行。
下面就来看,怎么写main.js。
如果我们的代码不依赖任何其他模块,那么可以直接写入javascript代码。
//main.js
alert("加载成功");
但这样的话,就没必要使用require.js了。真正常见的情况是,主模块依赖于其他模块,这时就要使用AMD规范定义的的require()函数。
//main.js
require(["moduleA","moduleB","moduleC"],function(moduleA,moduleB,moduleC){
//...
})
require()函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是[‘moduleA’, ‘moduleB’, ‘moduleC’],即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
模块化加载
require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。
require.config({
paths:{
aaa:"aaa'path"
}
})
上面的代码给出了三个模块的文件名,路径默认与main.js在同一个目录(js子目录)。如果这些模块在其他目录,比如js/lib目录,则有两种写法。一种是逐一指定路径。
//可以用 baseUrl: + 路径 改变基目录
//如果某个模块在另一台主机上,也可以直接指定它的网址
AMD模块的写法
require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。具体来说,就是模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。假定现在有一个add.js文件,它定义了一个add模块。那么,add.js就要这样写:
define(function(){
function add(x,y){
return x+y;
}
function show(){
console.log("hello world");
}
function ccc(){
console.log("ccc");
}
//对外暴露
return {
add:add,
show:show,
ccc:ccc
}
});
require.config({
paths:{
add:"demo/add",
mul:"demo/mul"
}
})
require(["add","mul"],function(addObj,mulObj){
var res = addObj.add(10 , 20);
alert(res);
addObj.show();
alert(mulObj.mul(10,10));
})
如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。
案例分析
-
首先在index下导入require.js
-
配置main.js
require.config({ paths: { "jquery": "jquery-1.11.3", "jquery-cookie": "jquery.cookie", 'parabola': "parabola", "index": "index", "banner": "banner" }, shim: { //设置依赖关系 先引入jquery.js 然后在隐去jquery-cookie "jquery-cookie": ["jquery"], //声明当前模块不遵从AMD "parabola": { exports: "_" } } }) require(['index', "banner"], function(index, banner){ index.index(); banner.banner(); })
-
将js代码修改成遵守AMD规范(写在define里面,对外暴露)
标签:function,模块化,require,js,开发,模块,moduleA,加载 来源: https://blog.csdn.net/qq_45902611/article/details/116334186