- 什么是前端模块化?
- 前端为什么需要模块化?
-
CommonJS、AMD、ES6、CMD区别是什么?
一、什么是模块化?
将一个复杂的程序依据一定的规则封装成几个块并进行组合在一起,而块的内部数据与实现是私有的, 只是向外部暴露一些方法与外部其它模块通信。
二、为什么需要模块化?
前端模块化还出现前,都是按功能分成几个js文件,然后再依次引入,举个例子,有a.js和b.js两个文件:
//a.js
var a = 1;
var b = 2;
//b.js
var b = 3;
var c = 4;
console.log(a);
然后在index.html里面引入这两个js。
<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>
上例有以下两个问题:
-
全局变量污染,
a.js和b.js都有全局变量b,如果两个文件同时引入就会导致变量覆盖。 -
文件依赖关系太复杂,
b.js依赖变量a,如果不先引入a.js的话就会报错。
随着前端项目越来越复杂,使得出错几率增加,代码更难以维护,那当时又是怎么解决的呢?用立即执行函数,它可以创建一个函数作用域,模块相关的变量都保存在函数作用域中,将需要的变量导出即可,将之前的代码改成以下代码:
//a.js
var moduleA = (function () {
return {
a: 1,
b: 2,
};
})();
//b.js
var moduleB = (function (moduleA) {
return {
b: moduleA.b,
c: 4,
};
})(moduleA);
这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。
三、CommonJS、AMD、ES6、CMD区别是什么?
CommonJS
首先说下CommonJS,node采用的CommonJS模块规范。每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见,它只能运行在node环境中,如果只是客户端开发的话不支持,它有如下几个特点:
-
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。require方法用于加载模块。 -
CommonJS具有缓存机制,模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。 -
CommonJS规范加载模块是同步的,只有加载完成才能执行后面的操作。
AMD
CommonJS功能很强大,但是只能运行在node端,那能不能在浏览器端也实现一套通过require和exports来实现模块的导入和导出呢?于是就诞生了AMD。使用AMD规范进行开发的时候需要引入第三方的库RequireJS。它主要解决以下两个问题:
- 多个
js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器。 -
js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长。
如何定义AMD模块:
define(moduleName, [dependencies], factory)
-
moduleName:定义模块名称,如果没有提供参数,默认为文件名 -
dependencies:可选,字符串数组,依赖的模块列表。 -
factory:必需,工厂方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值。
AMD推崇依赖前置,即当前模块依赖的其他模块,模块依赖必须在真正执行具体的factory方法前解决。
如何引用AMD模块:
require([dependencies], function(){})
-
dependencies:字符串数组,该模块的依赖。 -
callback:所依赖的模块都加载成功之后的回调函数,依赖的模块会以参数的形式传入该函数,从而在回调函数内部就可以使用这些模块。
来看下如何通过RequireJS实现AMD的模块化方案:
// a-module.js定义moduleA
define("moduleA", function () {
return {
a: 1,
};
});
// b-module.js定义moduleB
define("moduleB", ["moduleA"], function (moduleA) {
return {
b: moduleA.a + 1,
};
});
//index.js中引入模块
//引入模块之前需要配置模块路径
require.config({
paths: {
moduleA: "js/a-module",
moduleB: "js/b-module",
},
});
require(["moduleA", "moduleB"], function (moduleA, moduleB) {
console.log(moduleA);
console.log(moduleB);
});
CMD
CMD(Common Module Definition),通用模块定义,它解决的问题和AMD规范是一样的,只不过在模块定义方式和模块加载时机上不同,CMD也需要额外的引入第三方的库文件SeaJS。
CMD推崇就近依赖,只有在用到某个模块的时候再去require。
如何定义CMD模块:
CMD定义模块用define,它可以用来定义常量,对象。
//定义常量
define("hello world");
//定义对象
define({ name:"don" })
不过define最常用的还是定义函数:
define(function (require, exports, module) { ... });
当用CMD定义函数时,函数接受三个参数:
-
require:用来获取其他模块提供的接口。 -
exports:用来向外提供模块接口。 -
module:用来存储与当前模块相关的一些属性和方法。要注意的是它也可以用来导出。只通过exports参数来提供接口,有时无法满足开发者的所有需求。例如当模块的接口是某个类的实例时,就需要通过module.exports来实现。
如何引用CMD模块:
seajs.use([dependencies], callback)
-
dependencies:字符串数组,该模块的依赖。 -
callback:所依赖的模块都加载成功之后的回调函数,依赖的模块会以参数的形式传入该函数,从而在回调函数内部就可以使用这些模块。
// a-module.js定义moduleA
define({ a: 1 });
// b-module.js定义moduleB
define(function (require, exports) {
var moduleA = require("./a-module");
exports.b = moduleA.a + 1;
});
//index.js中引入模块
seajs.use(["./js/a-module.js", "./js/b-module.js"], function (moduleA, moduleB) {
console.log(moduleA);
console.log(moduleB);
});
ES6
CMD和AMD的模块化方案是非官方的,而为了统一前端模块化,ES官方在2015年推出了ES6的模块化,统一了客户端和服务端的模块化规范。
如何定义ES6模块:
// a-module.js定义a,b两个导出的变量
export const a = 1;
// b-module.js定义a,b两个导出的变量
const b = 3;
export default b;
如何引用ES6模块:
//直接import一个文件地址表示直接引入文件
import "https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.min.js";
//引入a-module.js
import { a } from "./a-module.js";
console.log(a);
//引入b-module.js
import b from "./a-module.js";
console.log(b);
ES6模块化有以下几个特点:
-
import模块只会导入一次,无论你引入多少次。 -
import/export是在编译就生成,而对应的require/export在使用的时候生成。 -
import/export模块输出的是值的引用,而require/exports输出的是一个值的拷贝。