# 模块化概述
模块化,即将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。可以更方便地使用别人的代码,想要什么功能,就加载什么模块。 例如CSS的@import
JavaScript原本的模块化可以通过函数或对象实现:
函数: 将几个函数封装到一个文件,需要的时候记载这个文件,调用其中函数即可。 但是这样会污染全局变量,造成命名冲突。
对象: 模块写成一个对象,模块成员都封装在对象里,通过调用对象属性,访问使用模块成员。但同时也暴露了模块成员,外部可以修改模块内部状态。
而JavaScript历史上并没有模块体系,无法将一个大程序拆分,对开发大型的、复杂的项目形成了巨大障碍。所以,社区制定了一些模块加载方案,最主要的有 CommonJS
和 AMD
、CMD
。
模块化开发的前提就是所有开发者必须以同样的方式编写模块,否则你有你的写法,我有我的写法,就会乱套,所以出现了四种规范 CommonJS,AMD,CMD,ES6模块化。
- CommonJS: 服务器端模块化,如Node.js
- AMD: 浏览器模块化,
RequireJS
在推广过程中对模块定义的规范化的产出 - CMD: 浏览器模块化,国内发展出来的,CMD有个浏览器的实现
SeaJS
,SeaJS
要解决的问题和requireJS
一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同 - ES6 Module: 浏览器模块化,ES6标准的模块化,用以取代 CommonJS 和 AMD 规范。
# AMD
AMD规范不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS
。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
# 1. 引入require.js文件
<script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>
# 2. 模块编写 (define)
require.js中,每个模块就是一个js文件。RequireJS定义了一个函数 define
,它是全局变量,用来定义模块:
define(id?, dependencies?, factory);
参数说明:
参数 | 值 | 说明 |
---|---|---|
id | string | (可选)模块名称 |
dependencies | array | (可选)依赖数组,表示该模块所依赖的模块,模块名不需要加js后缀 |
factory | array/object | 模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值 |
# 导出模块
require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。
假定现在顶一个模块math.js
,如果不需要依赖其他模块,则:
define(function(){
var num = 10;
function add(a, b){ return a + b };
// 声明了两个属性进行导出
return {
num,
add
}
})
# 导入模块
例子:main.js文件加载两个模块(jquery, math)并在回调函数中使用:
// main.js 文件
require(['jquery', 'math'], function ($, math){
// 回调函数中的math即为刚才导出的{ num, add }
alert( math.add(1,2) ) // 3
});
# 模块加载的配置
上面案例中,引入三个模块(jquery, math),默认情况下,require.js假定这两个模块与main.js在同一个目录,文件名分别为jquery.js
和math.js
,然后自动加载。
使用require.config()
方法,可以对模块的加载行为进行自定义:
require.config({
baseUrl: "./js",
paths: {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min",
"math": "lib/math.min", // 省略js后缀
}
});
配置项 | 值 | 说明 |
---|---|---|
baseUrl | string | 定义读取模块的根目录 |
paths | object | 定义模块路径与别名,如上例模块路径lib/math.min ,用math 指代 |
# 兼容性
# CMD
CMD规范是国内发展出来的,CMD的浏览器的实现是sea.js
,SeaJS与requireJS类似,只不过在模块定义方式和模块加载(运行、解析)时机上有所不同。
导入:
<script src="https://cdn.bootcss.com/seajs/3.0.3/sea.js"></script>
# CMD 模块编写
在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:
define(function(require, exports, module) {
// 模块代码
});
参数 | 说明 |
---|---|
require | 可以用来导入其他模块 |
exports | 可以把该模块内容属性方法导出 |
module | 一个对象,存储了与当前模块相关联的一些属性和方法 |
# 定义模块 (define)
// math.js文件
define(function(require, exports, module) {
require('./jquery.min.js') // require 导入jquery
$.ajax()
exports.add = function(a, b){ return a+b }; // export导出函数add
});
# 导入模块 (seajs.use)
seajs.use(['./math.js'], function (math) {
math.add(1, 2); // 3
});
# 配置Sea.js ( seajs.config() )
配置项 | 值 | 说明 |
---|---|---|
base | string | 定义读取模块的根目录 |
alias | object | 定义模块路径与别名,如模块路径../lib/jquery.min.js ,用jquery 指代 |
seajs.config({
base:"./js"
alias:{
'jquery':'../lib/jquery.min.js'
}
});
# AMD 与 CMD 的区别
- AMD是依赖关系前置,在定义模块的时候就要声明其依赖的模块;
- CMD是按需加载依赖就近,只有在用到某个模块的时候再去require:
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
# 兼容性
# ES6中的模块化
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict"
;。
注意:ES6模块中,顶层的
this
指向undefined
,即不应该在顶层代码使用this
。
模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
# 1. export命令
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。export
可以让我们把变量,函数,对象进行模块化,提供外部调用接口,让外部进行引用。
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
或在文件末尾多变量输出:
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
export
命令除了输出变量,还可以输出函数或类(class)。
exprot function fun(){ }
或
function fun() {}
exprot {fun};
有些时候并不想暴露模块里边的变量名称,还可使用as
关键字对变量进行重命名
export {
v1 as firstName,
v2 as lastName,
v3 as lastName
};
# 2. export default 命令
export
与export default
均可用于导出常量、函数、文件、模块等- 你可以在其它文件或模块中通过
import+(常量 | 函数 | 文件 | 模块)名
的方式,将其导入,以便能够对其进行使用 - 在一个文件或模块中,
export
、import
可以有多个,export default
仅有一个 - 通过
export
方式导出,在导入时要加{ }
,export default
则不需要
var name="李四";
export { name }
//import { name } from "./a.js"
可以写成:
var name="李四";
export default name
//import name from "./a.js" 这里name不需要大括号
# 3. import 命令
使用export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过import
命令加载这个模块。
export
对应的导入方式
export var a ='js';
export function add(a,b){
return a+b;
}
import {a,add} from './temp';
//也可以分开写
import a from './temp';
import add from './temp';
export defalut
对应的导入方式
var a ='js';
function add(a,b){
return a+b;
}
export defalut {a, add}
import obj from './temp';
- import用as方式引入 (多个变量用一个空对象来代理,你所有的方法和属性都是在types命名空间)
const LOGIN = 'login';
const LOGOUT = 'logout';
const TITLE = 'title'
export {LOGIN,LOGOUT,TITLE}
import * as types from './temp.js' //你所有的方法和属性都是在types命名空间
// 调用里面里面的值可以 这样做
types.LOGIN
types.LOGOUT
types.TITLE
由于ES6的模块化在浏览器端兼容性较差,不能直接在浏览器中预览,必须要使用Babel进行编译之后正常看到结果。