前端模块化

  • 什么是前端模块化?
  • 前端为什么需要模块化?
  • CommonJSAMDES6CMD区别是什么?
一、什么是模块化?

将一个复杂的程序依据一定的规则封装成几个块并进行组合在一起,而块的内部数据与实现是私有的, 只是向外部暴露一些方法与外部其它模块通信。

二、为什么需要模块化?

前端模块化还出现前,都是按功能分成几个js文件,然后再依次引入,举个例子,有a.jsb.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>

上例有以下两个问题:

  1. 全局变量污染a.jsb.js都有全局变量b,如果两个文件同时引入就会导致变量覆盖。
  2. 文件依赖关系太复杂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);

这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

三、CommonJSAMDES6CMD区别是什么?

CommonJS

首先说下CommonJSnode采用的CommonJS模块规范。每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见,它只能运行在node环境中,如果只是客户端开发的话不支持,它有如下几个特点:

  • CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。require方法用于加载模块。
  • CommonJS具有缓存机制,模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。
  • CommonJS规范加载模块是同步的,只有加载完成才能执行后面的操作。
AMD

CommonJS功能很强大,但是只能运行在node端,那能不能在浏览器端也实现一套通过requireexports来实现模块的导入和导出呢?于是就诞生了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

CMDAMD的模块化方案是非官方的,而为了统一前端模块化,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输出的是一个值的拷贝。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容