模块化的演进过程

Stage 1 - 文件划分方式

  1. └─ stage-1
  2. ├── module-a.js
  3. ├── module-b.js
  4. └── index.html

缺点

  • 模块直接在全局工作,大量模块成员污染全局作用域;
  • 没有私有空间,所有模块内的成员都可以在模块外部被访问或者修改;
  • 一旦模块增多,容易产生命名冲突
  • 无法管理模块与模块之间的依赖关系;
  • 在维护的过程中也很难分辨每个成员所属的模块

Stage 2 – 命名空间方式

约定每个模块只暴露一个全局对象,所有模块成员都挂载到这个全局对象

  1. // module-a.js
  2. window.moduleA = {
  3. method1: function () {
  4. console.log('moduleA#method1')
  5. }
  6. }
  7. // module-b.js
  8. window.moduleB = {
  9. data: 'something'
  10. method1: function () {
  11. console.log('moduleB#method1')
  12. }
  13. }

只是解决了命名冲突的问题,但是其它问题依旧存在。

Stage 3 – IIFE

  1. // module-a.js
  2. ;(function () {
  3. var name = 'module-a'
  4. function method1 () {
  5. console.log(name + '#method1')
  6. }
  7. window.moduleA = {
  8. method1: method1
  9. }
  10. })()
  11. // module-b.js
  12. ;(function () {
  13. var name = 'module-b'
  14. function method1 () {
  15. console.log(name + '#method1')
  16. }
  17. window.moduleB = {
  18. method1: method1
  19. }
  20. })()

这种方式带来了私有成员的概念,私有成员只能在模块成员内通过闭包的形式访问,这就解决了前面所提到的全局作用域污染和命名冲突的问题

在 IIFE 的基础之上,我们还可以利用 IIFE 参数作为依赖声明使用,这使得每一个模块之间的依赖关系变得更加明显。

  1. // module-a.js
  2. ;(function ($) { // 通过参数明显表明这个模块的依赖
  3. var name = 'module-a'
  4. function method1 () {
  5. console.log(name + '#method1')
  6. $('body').animate({ margin: '200px' })
  7. }
  8. window.moduleA = {
  9. method1: method1
  10. }
  11. })(jQuery) // 传入依赖

模块加载的问题

通过 script 标签的方式直接在页面中引入的这些模块,这意味着模块的加载并不受代码的控制,时间久了维护起来会十分麻烦。

模块化方案

  • AMD/CMD
  • CJS/ESM

打包工具

  • 第一,它需要具备编译代码的能力,也就是将我们开发阶段编写的那些包含新特性的代码转换为能够兼容大多数环境的代码,解决我们所面临的环境兼容问题
  • 第二,能够将散落的模块再打包到一起,这样就解决了浏览器频繁请求模块文件的问题。这里需要注意,只是在开发阶段才需要模块化的文件划分,因为它能够帮我们更好地组织代码,到了实际运行阶段,这种划分就没有必要了。
  • 第三,它需要支持不同种类的前端模块类型,也就是说可以将开发过程中涉及的样式、图片、字体等所有资源文件都作为模块使用,这样我们就拥有了一个统一的模块化方案,所有资源文件的加载都可以通过代码控制,与业务代码统一维护,更为合理。