前端模块化演进过程

模块化的演进过程

  1. Stage 1 - 文件划分方式
  2. Stage 2 – 命名空间方式
  3. Stage 3 - IIFE
  4. Stage 4 - IIFE 依赖参数
Stage 1 - 文件划分方式
1
2
3
4
└─ stage-1
    ├── module-a.js
    ├── module-b.js
    └── index.html
1
2
3
4
// module-a.js 
function foo ({
   console.log('moduleA#foo')
}
1
2
// module-b.js 
var data = 'something'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Stage 1</title>
</head>
<body>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    // 直接使用全局成员
    foo() // 可能存在命名冲突
    console.log(data)
    data = 'other' // 数据可能会被修改
  </script>
</body>
</html>

缺点:实现方式完全依靠约定实现

  • 模块直接在全局工作,大量模块成员污染全局作用域;
  • 没有私有空间,所有模块内的成员都可以在模块外部被访问或者修改;
  • 一旦模块增多,容易产生命名冲突;
  • 无法管理模块与模块之间的依赖关系;
  • 在维护的过程中也很难分辨每个成员所属的模块。
Stage 2 - 命名空间方式
1
2
3
4
5
6
// module-a.js
window.moduleA = {
  method1: function ({
    console.log('moduleA#method1')
  }
}
1
2
3
4
5
6
7
// module-b.js
window.moduleB = {
  data: 'something'
  method1: function ({
    console.log('moduleB#method1')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Stage 2</title>
</head>
<body>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    moduleA.method1()
    moduleB.method1()
    // 模块成员依然可以被修改
    moduleA.data = 'foo'
  </script>
</body>
</html>

这种命名空间的方式只是解决了命名冲突的问题,但是其它问题依旧存在。

Stage 3 - IIFE
1
2
3
4
5
6
7
8
9
10
11
12
// module-a.js
;(function ({
  var name = 'module-a'

  function method1 ({
    console.log(name + '#method1')
  }

  window.moduleA = {
    method1: method1
  }
})()
1
2
3
4
5
6
7
8
9
10
11
12
// module-b.js
;(function ({
  var name = 'module-b'

  function method1 ({
    console.log(name + '#method1')
  }

  window.moduleB = {
    method1: method1
  }
})()

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

Stage 4 - IIFE依赖参数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// module-a.js
;(function ($// 通过参数明显表明这个模块的依赖
  var name = 'module-a'

  function method1 ({
    console.log(name + '#method1')
    $('body').animate({ margin'200px' })
  }

  window.moduleA = {
    method1: method1
  }
})(jQuery)

模块加载的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
  <title>Evolution</title>
</head>
<body>
  <script src="https://unpkg.com/jquery"></script>
  <script src="module-a.js"></script>
  <script src="module-b.js"></script>
  <script>
    moduleA.method1()
    moduleB.method1()
  </script>
</body>
</html>

虽然都解决了模块代码的组织问题,但模块加载的问题却被忽略了

模块的加载并不受代码的控制

模块化规范

  • CommonJS 规范

    它是 Node.js 中所遵循的模块规范,该规范约定,一个文件就是一个模块,每个模块都有单独的作用域,通过 module.exports 导出成员,再通过 require 函数载入模块。
    Node.js 执行机制是在启动时加载模块,执行过程中只是使用模块,所以这种方式不会有问题。

  • AMD(Asynchronous Module Definition)

    代表:Require.js。除了实现了 AMD 模块化规范,本身也是一个非常强大的模块加载器。
    AMD定义模块
    AMD载入模块

  • CMD (Common Module Definition)

    代表:Sea.js。
    写法类似 CommonJs 规范。
    Sea.js 被 Require.js 兼容了,下图
    Sea.js 被 Require.js 兼容了

模块化的标准规范

  • 在 Node.js 环境中,我们遵循 CommonJS 规范来组织模块。
  • 在浏览器环境中,我们遵循 ES Modules 规范。

    ES Modules 是 ECMAScript 2015(ES6)中才定义的模块系统,兼容性,借助webapck打包工具

模块打包工具的出现

  • 解决兼容问题。
    解决兼容问题
  • 模块化的方式划分出来的模块文件过多,浏览器的频繁发送网络请求,影响应用的工作效率。
    将散落的模块再打包到一起
  • HTML 和 CSS 这些资源文件也会面临需要被模块化的问题。
    支持不同种类的前端模块类型

拉勾教育学习笔记,非原创。

坚持原创技术分享,您的支持将鼓励我继续创作!