前端模块化

  • 什么是前端模块化?
  • 前端为什么需要模块化?
  • 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输出的是一个值的拷贝。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容