刚学前端的时候,曾有一段时间很迷糊,不知道为啥突然从html文件、js文件和css文件三件套,变成需要打包在服务器才能用了。这种不明白感,随着使用vue,weex等框架逐步熟练之后,降低不少。但依然不知道,这一路究竟发生了什么。
0,模块的定义
模块和模块化:
模块:
在软件系统设计中,模块是一个拥有明确定义的输入输出和特性的程序实体。如果模块的所有输入都是实现功能必不可少的,所有输出都有动作产生,那么该模块即成为具有明确意义的模块。也就是说,如果少了一个输入,模块就不能实现全部功能,它没有不必要的输入,每个输入都用于产生输出,每个输出都是模块执行某一功能的结果,没有未经模块的转换就变成输出的输入。
总的来说,一般模块具有以下几种特征:
接口,模块的输入输出
功能,指模块实现的功能,有什么作用
逻辑,描述模块内部如何实现需求及所需数据
状态,指该模块的运行环境,模块间调用与被调用关系。
模块化:
模块化就是将程序划分成若干个独立的模块,每个模块完成一个特定子功能,每个模块即相对独立,又相互联系,他们共同完成系统指定的各项功能。 模块化的目的是为了降低软件的复杂性。对软件进行适当的分解,不但可以降低复杂性,而且可以减少开发工作量,从而降低软件开发成本。
1, 前端最开始的处理:
最自然的写法:
function foo(){
//...
}
function bar(){
//...
}
而只将相关的代码放一起这种写法缺点很明显:
1:大量的函数名会"污染"全局变量,无法保证不与其他模块发生变量名冲突,
2:模块成员之间的关系不能清晰看出。
3:没有私有变量和方法等。
针对问题1:和2:,可以用对象来缓解。
var moduleA = {
foo : function () {},
bar: function () {}
}
但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。
针对这个问题,可以采用立即执行函数来处理:
var MODULE = (function () {
var my = {}, privateVariable = 1;
function privateMethod() {
// method that use in module
}
my.moduleProperty = 1;
my.moduleMethod = function () {
// module API.
};
return my;
}());
立即执行函数内部声明的变量和函数,为模块内部私有,return对象为供外部调用的接口。
如此方式的“模块”还可以扩展、继承、添加子模块等等。
详细参见:js模块的基本方法
如此便形成了前端模块的雏形。
2, 前端为什么需要模块化。
当下前端的快速发展,对网页的构建提出了更高要求:
Web sites are turning into Web Apps
Code complexity grows as the site gets bigger
Highly decoupled JS files/modules is wanted
Deployment wants optimized code in few HTTP calls
前端通过
当逻辑复杂时,打开方式如下:
body
script(src="zepto.js")
script(src="jhash.js")
script(src="fastClick.js")
script(src="iScroll.js")
script(src="underscore.js")
script(src="handlebar.js")
script(src="datacenter.js")
script(src="deferred.js")
script(src="util/wxbridge.js")
script(src="util/login.js")
script(src="util/base.js")
script(src="util/city.js")
script(src="util/date.js")
script(src="util/cookie.js")
script(src="app.js")
各个文件的引入顺序非常重要,各个文件并行加载、DOM 顺序即执行顺序
而用script标签来引入js函数文件存在问题:
维护困难
依赖模糊
请求过多
只用引入
在有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者兼容,一个模块不用修改,在服务器和浏览器都可以运行。但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。
var math = require('math');
math.add(2, 3);
如上面的代码,math.add实在require之后运行,因此必须等math.js加载完成。
这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间而使浏览器处于"假死"状态。因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。
4,AMD规范
RequireJS是一个工具库,主要用于客户端的模块管理。它可以让客户端的代码分成一个个模块,实现异步或动态加载,从而提高代码的性能和可维护性。它的模块管理遵守AMD规范(Asynchronous Module Definition)。
阮一峰:RequireJS和AMD规范
RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。
4.1 嵌入网页
<script data-main="scripts/main" src="scripts/require.js"></script>
代码的data-main属性不可省略,用于指定主代码所在的脚本文件
4.2 define 方法:定义模块
按照是否依赖其他模块,可以分成独立模块和非独立模块。
独立模块的写法:
define({
method1: function() {},
method2: function() {},
});
// 当然也可以写为如下方式
define(function () {
return {
method1: function() {},
method2: function() {},
};
});
return 返回的是模块的接口。
非独立模块
define(['module1', 'module2'], function(m1, m2) {
...
});
4.3 require方法:调用模块
require(['foo', 'bar'], function ( foo, bar ) {
foo.doSomething();
});
// 当有过多依赖时,可以用commonJS 写法:
define(
function (require) {
var dep1 = require('dep1'),
dep2 = require('dep2'),
dep3 = require('dep3'),
dep4 = require('dep4'),
...
}
});
上面方法表示加载foo和bar两个模块,当这两个模块都加载成功后,执行一个回调函数。该回调函数就用来完成具体的任务。
require方法也可以用在define方法内部。比如上面的commonJS风格的写法使用。
5, CMD规范
CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,CMD有个浏览器的实现是玉伯的SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同。
以下是对seajs使用的简单介绍:
5.1 seajs页面的引入
<body>
<h1 id="title">seajs demo</h1>
<script src="sea-module/sea.js"></script>
<script>
seajs.use('./static/main.js');
</script>
</body>
首先是引入sea.js文件,然后是通过seajs.use加载main.js文件。main.js是一个“入口”文件。
5.2 模块的写法
比如定义一个changeText.js模块
define(function (require, exports, module) {
var textContent = 'message from module changeText';
module.exports = {
text: textContent
}
})
5.3 引入模块
define(function (require, exports, module) {
var changeText = require('../static/changeText.js');
var title = document.getElementById('title');
title.innerHTML = changeText.text;
})
此时,大功告成,会在html页面上显示 “message from module changeText"
5.4 别名 (seajs.config, alias)
seajs.config({
alias:{
'changeText':'../static/changeText.js'
}
});
通过config中alias给’…/static/changeText.js’设置了别名changeText,现在main中引用changeText模块就可以直接写成这样了var changeText = require(‘changeText’)。
5.5 seajs.use回调函数
seajs.use(['main','jquery'],function(main,$) {
$('#title').after('<button id="show">showText</button>');
$('#show').on('click',function() {
main.showText()
})
});
上述代码我们加载了两个模块,并把它们输出的对象传参给main和$变量,通过点击事件调用main中的showText方法,而showText方法调用了changeText中的init方法
参考链接: