- 什么是前端模块化?
- 前端为什么需要模块化?
-
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
输出的是一个值的拷贝。