声明
本篇内容摘抄自以下来源:
- Github:smyhvae/web
- JavaScript模块化开发的演进历程
- JavaScript模块化七日谈
- ES6:Module 的加载实现
- CommonJS规范
感谢各位大佬的分享,解惑了很多。
正文-模块化
现在回过头来想想,也许选择以《JavaScript权威指南》一书来作为入门有些不好,因为这本书毕竟是很早之前的,书中所讲的思想、标准也基本都只是 ES5 及那时代的相关技术。
这也就导致了,在书中看到的很多例子,虽然觉得所用到的思想很奇妙,比如临时命名空间之类的处理,但其实,有些技术到现在已经有了更为强大的技术替代了。
就像这篇要讲的模块化,目前,以我看到的各资料后,所收获的知识是,大概有四种较为常用且热门的模块化技术,也许还有更新的技术,也许还有我不知道的技术,无所谓,慢慢来,这篇的内容已经够我消化了。
目前四种模块化技术:
- CommonJS规范&node.js
- AMD规范&Require.js
- CMD规范&Sea.js
- ES6标准
前面是规范,规范就是类似于 ES5,ES6 只是提出来作为一种标准规范,而不同规范有具体的实现,比如 nodeJS 实现了 CommonJS 规范。
模块化历程
在声明部分中的第二、第三链接里那两篇,以时间线介绍了模块化的相关技术的发展历程,我觉得很有必要一看,对于掌握和理解目前的模块化技术挺有帮助的。
这里,就稍微摘抄其中一些内容,详细内容还是需要到原文阅读。
- 全局变量、全局函数(1999年)
这时候的多个 js 脚本文件之间,直接通过全局变量或全局函数的方式进行通信,这种方式叫做:直接定义依赖。
虽然做的好一些的会对这些 js 文件做一些目录规划,将资源归类整理,但仍无法解决全局命名空间被大量污染,极其容易导致变量冲突问题。
- 对象作为命名空间(2002年)
为了解决遍地的全局变量问题,这时候提出一种命名空间模式的思路,即将本要定义成全局变量、全局函数的这些全都作为一个对象的属性存在,这个对象的角色充当命名空间,不同模块的 JS 文件中通过访问这个对象的属性来进行通信。
- 立即执行的函数作为临时命名空间 + 闭包(2003年)
虽然提出利用一个对象来作为命名空间的思路,一定程度解决了大量的全局变量的问题,但仍旧存在很多局限,比如没有模块的隐藏性,所以针对这些问题,这时候又新提出一种思路:利用立即执行的函数来作为临时命名空间,这样就可以避免污染全局命名空间,同时,结合闭包特性,来实现隐藏细节,只对外暴露指定接口。
虽然这种思路,解决了很多问题,但仍旧有一些局限,比如,缺乏管理者,什么意思,就是说,在前端里,开发人员得手动将不同 JS 脚本文件按照它们之间的依赖关系,以被依赖在前面的顺序来手动书写
当然,这只是基础用法的步骤,其中第 3 步的模块初始化步骤也可通过其他方式,如直接利用 require 的不同参数类型来实现等等,但大体上需要这几个过程,尤其是最后一步,也是最重要一步,因为 AMD 在前端的具体实现都依靠于 Require.js,所以必须等到 Require.js 下载并执行结束后会开始处理其他 js。
以上例子的项目结构如图:
小结
最后小结一下,AMD 规范的具体实现 Require.js 其实从使用上来看,已经比较容易明白它的原理是什么了。
本质上,也还是利用了函数的特性,作为模块存在的那些代码本身已经通过 define 规范被定义在函数内了,所以模块内的代码自然对外是隐藏的,外部能访问到的只有函数内 return 的接口,那么这里其实也就利用了闭包的特性。
所以,模块化的实现,无非就是让函数作为临时命名空间结合闭包或者对象作为命名空间方式, 这种方式即使没有 CommonJS 规范,没有 AMD 规范,自己写代码很可以容易的实现。那么为什么会有这些规范技术的出现呢?
无非就是为了引入一个管理者的角色,没有管理者的角色,模块之间的依赖关系,哪个文件先加载,哪个后加载,
使用步骤跟 AMD 很类似,首先是需要依赖于 Sea.js,所以必须先下载它。
然后定义模块、依赖模块、使用模块的方式就跟 CommonJS 很类似,这几个操作跟 AMD 会有些不同,也许这点也正是 CMD 存在的原因之一。
最后一步也是最重要一步,需要在 HTML 文档中加载 sea.js 文档,并指定入口的 js,注意做的事虽然跟 AMD 一样,但实现方式不一样。
小结
其实,CMD 跟 CommonJS 很类似,甚至在模块化方面的工作,可以很通俗的将 sea.js 理解成 node.js 所做的事,只是有些 node.js 能完成但却无法通过 sea.js 来负责的工作需要开发人员手动处理,比如定义一个模块、通过
CMD 适用的前端浏览器的运行环境也没有 Sea.js,所以项目中也需要先加载 Sea.js,然后再执行主入口的 js 代码,需要在 HTML 中使用类似如下命令:
特性
AMD:依赖前置、提前执行,如:
require(['module1','module2'], function(m1, m2){
//...
})
define(['module1','module2'], function(m1, m2){
//...
})
需要先将所有的依赖的加载完毕后,才会去处理回调中的代码,这叫依赖前置、提前执行;
CMD:依赖就近、延迟执行,如:
define(function(require, exports, module){
//...
var module1 = require("./module1");
//...
require("./module2", function(m2){
//...
});
})
等代码执行到 require 这行代码时才去加载对应的模块
ES6标准
ES6 中新增的模块特性,在上一篇中已经稍微介绍了点,这里也不具体展开介绍了,需要的话开头的声明部分有给出链接,自行查阅。
这里就简单说下,在前端浏览器中使用 ES6 模块特性的步骤:
定义模块,通过指定