在系统学习ES6语法过程中,才真正地理解到前端模块化编程的思想,所以决定写下这篇文章,让更多的人体会到前端模块化。但是这篇文章并不会直接讲述ES6中的模块语法,而是通过讲述ES6之前的模块化规范,从而引出ES6模块化,因为不了解历史就无法认识
如果你想直接了解ES6模块化可以直接跳过篇文章,请移步ES6语法—Module模块化(下)
读完这篇文章,你能收获到
前端模块化的意义
模块的写法
CommandJS规范
浏览器环境
AMD规范&&RequireJS
CMD规范&&SeaJS
ES6(ES2017)之前,前端模块规范有三种: CommandJS、AMD和CMD
CommandJS用在服务端模块化规范,AMD和CMD用在浏览器模块化规范
CommandJS: NodeJS
AMD: RequireJS
CMD: SeaJS
CommandJS:同步加载
AMD:提前执行(异步加载,回调执行)
CMD:延迟执行(运行时按需加载,顺序执行)
前端模块化的意义
- 解决命名冲突,防止全局变量的污染
- 管理文件依赖
- 保护私有属性
- 易于代码的扩展和维护
模块的写法
-
函数的写法
只要把不同的函数简单地放在一起,就算是一个模块
function f1(){
//...
}
function f2(){
//...
}
这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
-
对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
//调用这个对象的属性
module1.m1()
但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
module1.count = 5
- 立即执行函数写法(IIFE)
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
使用上面的写法,外部代码无法读取内部的_count变量
console.info(module1._count); //undefined
module1就是Javascript模块的基本写法。下面,再对这种写法进行加工。
- 放大模式
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块。
-
输入全局变量
独立性是模块的重要特点,模块内部最好不要和程序的其他部门直接交互
为了在模块内部调用全局变量,必须显式地将全局变量输入模块
var module1 = (function($){
//...
})(jQuery)
CommandJS规范
CommonJS是服务器端模块的规范,由Node推广使用。在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务端,一定要有模块,与操作系统和其他应用程序,否则根本没法编程
node.js的模块系统,就是参照CommonJS规范实现的。
根据CommonJS中:
- 一个单独的文件就是一个模块。每个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为
global
对象的属性 - 输出模块属性和方法可以用module.exports或者exports对象
- 载入模块使用
require()
方法,该方法是运行时加载,生成一个对象,可以读取模块的属性和方法
//index.js
var math = require('math');
然后,就可以调用模块提供的方法:
//math.js
exports.add = function(a, b){
return a + b
}
//index.js
var math = require('math');
math.add(2,3); // 5
仔细观察上面的代码, 你会注意到require()
是同步的。模块系统需要同步读取模块文件内容,并编译执行以得到模块接口
浏览器环境
有了服务端模块以后,很自然地,大家就要要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行
但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,会有一个很大的问题,你能看得出来吗?
//index.js
var math = require('math');
math.add(2,3); // 5
嘿,哥们,上面已经透露了,require()
是同步执行的,因此第二行代码math.add(2,3)
必须等math.js
加载完成。也就是说,如果加载时间很长,浏览器整个页面就会阻塞
同步加载对服务器不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待的时间就是硬盘的读取时间,速度非常快。但是对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于“假死状态”
因此,浏览器端的模块,不能采用“同步加载(synchronous)”,只能采用“异步加载(asynchronous)”.这就是AMD规范诞生的背景
AMD规范
AMD是Asynchronous Module Definition
的缩写,意思就是“异步模式定义”.它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个会调用函数中,等到加载完成之后,这个回调函数才会运行.
AMD也采用require()
语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
第一个参数[module]是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码写成AMD形式,就是下面这样:
requrie(['math], function(math){
math.add(2, 3);
})
math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。
实际上AMD是RequireJS
在推广过程中对模块定义的规范户的产出
RequireJS
主要解决两个问题
- 实现js文件的异步加载,避免网页失去响应
- 管理模块之间的依赖关系,便于代码的编写和维护
define()函数
RequireJS定义了一个函数 define
,它是全局变量,用来定义模块
define(id?, dependencies?, factory);
参数说明:
- id:指定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
- 依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。
依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。 - 工厂方法factory,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
来举个🌰看看:
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
RequireJs使用例子
require.config
是用来定义别名的,在paths属性下配置别名。然后通过requirejs(模块数组, callback)
;参数一是数组,传入我们需要引用的模块名,第二个参数是个回调函数,传入一个变量,代替刚才所引入的模块
//第一步 引入require.js
<script src="js/require.js"></script>
//main.js
//别名配置
requirejs.config({
paths: {
jquery: 'jquery.min' //可以省略.js
}
})
//引入模块,用变量$表示jquery模块
requriejs(['jquery'], function($){
$('body').css('backgroundColor','red');
})
引入模块也可以只写require()。requirejs
通过define()
定义模块,定义的参数上同。在模块内的方法和变量外部是无法访问的,只有通过return
返回才行
//math.js
define('math',['jquery'], function ($) {//引入jQuery模块
return {
add: function(x,y){
return x + y;
}
};
});
main.js引入模块方法
//main.js
require(['jquery','math'], function ($,math) {
console.log(math.add(10,100));//110
});
CMD规范
CMD即Common Module Definition
通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS
,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS
一样,只不过在模块的定义方式和模块加载(运行、解析)的时机上有所不同
在CMD规范中吗,一个模块就是一个文件。代码的书写格式如下:
define([require, exports, module], function(require, exports, module) {
// 模块代码
});
require
是可以把其他模块导入进来的一个参数;而exports是可以把模块内的一些属性和方法导出的;module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。
AMD是提前加载,并且是异步加载所依赖的模块
CMD是延迟加载,运行时按需加载模块
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 提前加载
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
//CMD默认推荐的是
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 延迟加载
b.doSomething()
// ...
})
seajs使用例子
// 定义模块 myModule.js
define(function(require, exports, module) {
var $ = require('jquery.js')
$('div').addClass('active');
exports.data = 1;
});
///加载模块
seajs.use(['myModule.js'], function(myModule){
var star= myModule.data;
console.log(star); //1
})
ES6语法—Module模块化(上)到这里就已经结束了,下一篇文章ES6语法—Module模块化(下)将会讲述ES6Module语法完全取代CommandJS和AMD规范,成为浏览器和服务器通用的模块解决方案
参考
Javascript模块化编程(一):模块的书写
Javascript模块化编程(二):AMD规范
Javascript模块化编程(三):require.js的用法
前端模块化开发的价值
RequireJS下载地址
SeaJS官方demo下载地址
Seajs使用实例入门介绍
网友评论