前端??榛?/h1>

0. 为什么要模块化

模块化是解决应用系统与技术平台越来越复杂,越来越庞大问题的一个重要途径。无论是开发人员还是产品最终用户,都不希望为了系统中一小块的功能而不得不下载、安装、部署及维护整套庞大的系统。站在整个软件工业化的高度来看,??榛墙⒏髦止δ艿谋曜蓟那疤?。

1. 什么是??榛?/h2>

??榛墙桓龈丛拥某绦蛞谰菀欢ǖ墓嬖?规范)封装成几个块(文件), 并组合在一起。块(文件)的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它??橥ㄐ拧?/p>

在没有??榛?,前端将不同的功能封装到不同的全局函数中,如下:

function f1(){ ....}
function f2(){.....}

那么带来的问题就是:

  1. 全局的命名空间污染,容易导致命名冲突
  2. 不能保证和其他??槠鸪逋?/li>
  3. ??槌稍敝淇床怀鲋苯庸叵?。

为了解决这个问题,出现了namespace模式,如下:

let myNamespace = {
  data:"www.baidu.com",
  f1() {... }
}
//通过namespace避免污染命名空间
myNamespace.f1()  

但是以上方法仍然存在问题: 数据不安全。例如,可以直接修改里面的data

myNamespace.data = 'other data'

为了解决这个问题,出现了IIFE(Immediately-invoked function expression 立即执行函数表达式)模式,如下:

// index.html文件
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
    myModule.foo()
    myModule.bar()
    console.log(myModule.data) //undefined 不能访问??槟诓渴?    myModule.data = 'xxxx' //不是修改的??槟诓康膁ata
    myModule.foo() //没有改变
</script>
// module.js文件
(function(window) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    //用于暴露有函数
    console.log(`foo() ${data}`)
  }
  function bar() {
    //用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar } //ES6写法
})(window)

由于module中的data没有暴露,所以是不可修改的,代码myModule.data只会在window.myModule这个对象上新增一个data的属性。
但是以上方法仍然存在一个问题,如果module模块需要依赖其他??樵趺窗欤?需要对IIFE进行增强,如下:

 // index.html文件
  <!-- 引入的js必须有一定顺序 -->
  <script type="text/javascript" src="jquery-1.10.1.js"></script>
  <script type="text/javascript" src="module.js"></script>
  <script type="text/javascript">
    myModule.foo()
  </script>
// module.js文件
(function(window, $) {
  let data = 'www.baidu.com'
  //操作数据的函数
  function foo() {
    //用于暴露有函数
    console.log(`foo() ${data}`)
    $('body').css('background', 'red')
  }
  function bar() {
    //用于暴露有函数
    console.log(`bar() ${data}`)
    otherFun() //内部调用
  }
  function otherFun() {
    //内部私有的函数
    console.log('otherFun()')
  }
  //暴露行为
  window.myModule = { foo, bar }
})(window, jQuery)

以上就是前端的??榛?/p>

2. 什么是模块化规范

IIFE增强版的??榛导噬弦丫迪至饲岸说哪?榛?,它带来的好处包括

  • 避免了命名冲突(减少了全局命名空间的污染)
  • 更好地分离代码和数据安全
  • 更高的可维护性和可复用性

但是IIFE增强版的模块化带来了新的问题,比如:

  • 请求过多,首先我们要依赖多个???,那样就会发送多个请求,导致请求过多
  • 依赖模糊,我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。

为了解决这两个问题,出现了??榛娣?,比如 CommonJS, AMD, ES6, CMD规范。

3. ??榛娣?-CommonJS

基本语法:

//暴露模块 
module.export  = value 或  export.xxx = value
//引用???如果是第三方模块,xxx为模块名;如果是自定义???,xxx为模块文件路径
require("xxx")

CommonJS规范规定,每个??槟诓?,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。例:

// example.js
var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

上面代码通过module.exports输出变量x和函数addX。

//如果参数字符串以“./”开头,则表示加载的是一个位于相对路径
var example = require('./example.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6

require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该??榈膃xports对象。如果没有发现指定???,会报错。

CommonJS??榈募釉鼗剖?,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,??槟诓康谋浠陀跋觳坏秸飧鲋?。这点与ES6模块化有重大差异(下文会介绍)

3. ??榛娣?- AMD

CommonJS规范加载??槭峭降?,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载???,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

基本语法:

//定义没有依赖的模块
define(function(){
   return ???})

//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
   return ???})
 //引用??槭褂?require(['module1', 'module2'], function(m1, m2){
   使用m1/m2
})

AMD规范的典型实现是RequireJS,它是一个工具库,主要用于客户端的??楣芾?。通过define方法,将代码定义为模块;通过require方法,实现代码的??榧釉亍?/p>

4. ??榛娣?- CMD

CMD规范专门用于浏览器端,模块的加载是异步的,??槭褂檬辈呕峒釉刂葱?。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript ??槎甲裱?CMD??槎ㄒ骞娣?。

基本语法:

//定义没有依赖的模块
define(function(require, exports, module){
  exports.xxx = value
  module.exports = value
})

//定义有依赖的???define(function(require, exports, module){
  //引入依赖模块(同步)
  var module2 = require('./module2')
  //引入依赖???异步)
    require.async('./module3', function (m3) {
    })
  //暴露???  exports.xxx = value
})
 

define(function (require) {
  var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()
})
 

5. ??榛娣?- ES6

ES6 ??榈纳杓扑枷胧蔷×康木蔡沟帽嘁胧本湍苋范?榈囊览倒叵怠⑹淙牒褪涑龅谋淞?。CommonJS 和 AMD ??椋贾荒茉谠诵惺比范ㄕ庑┒?。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

基本语法:
export命令用于规定??榈亩酝饨涌?,import命令用于输入其他??樘峁┑墓δ堋?/p>

/** 定义???math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载???,就要用到export default命令,为??橹付鲜涑觥?/p>

// export-default.js
export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo'

??槟鲜涑? 其他??榧釉馗媚?槭?,import命令可以为该匿名函数指定任意名字。

ES6 ??橛?CommonJS ??榈牟钜?br> 它们有两个重大差异:

  • CommonJS ??槭涑龅氖且桓鲋档目奖?,ES6 ??槭涑龅氖侵档囊?。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 ??椴皇嵌韵?,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

ps: RequireJs 和 Sea.js 的实现其输出也是一个值的拷贝

6.总结

  • CommonJS规范主要用于服务端编程,加载??槭峭降?,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。
  • AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个???。不过,AMD规范开发成本高,代码的阅读和书写比较困难,??槎ㄒ宸绞降挠镆宀凰吵?/li>
  • CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,??榈募釉芈呒?/li>
  • ES6 在语言标准的层面上,实现了??楣δ埽沂迪值孟嗟奔虻?,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

参考自:

1. 前端??榛杲?完整版)
2. Module 的语法
3. Module 的加载实现
4.【前端工程化系列】简谈前端??榛?/em>开发与开发规范
5. 前端模块化开发 - haoxl - 博客园
6. 详解JavaScript??榛?/em>开发 - trigkit4 - SegmentFault 思否
7. 前端??榛?/em>:CommonJS,AMD,CMD,ES6 - 掘金
8. 浅谈前端??榛?/em>- 腾讯Web前端IMWeb 团队社区| blog | 团队博客
9. 前端工程之??榛? FEX
10. 前端模块化** - 谦行 - 博客园
11 前端??榛?/em>和组件化理解- Counting Stars - SegmentFault 思否
12. js模块化历程

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者

  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容