模块化的演进过程

前端工程化模块化开发

# Stage1 —— 文件划分方式(web中最原始的模块系统)modules

# 具体做法

将不同的功能和不同的数据存放到不同的js文件当中。约定每个文件就是一个独立的模块。在使用的时候在html文件中用script标签引入,然后我们用内联方式对全局成员进行调用

<body>
    <!--在html文件中用script标签引入-->
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
    <script>
        // 用内联方式对全局成员进行调用
        method1()
        method2()
    </script>
</body>
1
2
3
4
5
6
7
8
9
10

# 存在的问题

  1. 污染全局作用域 —— 所有的模块在全局工作,并没有独立的私有空间,暴露在全局很容易被修改
  2. 命名冲突问题 —— 模块多了之后很容易有命名冲突的问题
  3. 无法管理模块依赖关系

早起的模块化完全依靠约定,上了体量之后就无法解决了。

# Stage2 —— 命名空间方式

# 具体做法

每个模块只暴露一个全局的对象,所有的模块成员都挂载到这个对象下面。

// module-a.js
var moduleA = {
    name: 'module-a',
    method1: function () {
        console.log(this.name + 'use methos1')
    }
}

// module-a.js
var moduleB = {
    name: 'module-b',
    method2: function () {
        console.log(this.name + 'use methos2')
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
    <script>
        moduleA.method1()
        moduleB.method2()
        console.log(moduleA.name)
        // 模块成员可以被修改
        moduleA.name = 'foo'
    </script>
</body>
1
2
3
4
5
6
7
8
9
10
11

# 解决的问题

  • 命名冲突的问题

# 存在的问题

  1. 污染全局作用域 —— 所有的模块仍然在全局工作,没有独立的私有空间,模块内部成员容易被访问被修改
  2. 模块依赖关系也没有得到解决

# Stage3 —— IIFE(立即执行函数)

我们用立即实行函数为我们的模块提供独立的私有空间。

# 具体做法

我们将内部成员都放在一个匿名函数提供的私有作用域当中,对于要暴露给外界的内容,我们采用挂载到全局对象上的方式实现。

;(function () {
    var name = 'module-a'
    
    function method1 () {
        console.log( name + 'use method1')
    }
    
    window.moduleA = {
        method1: method1
    }
})()
1
2
3
4
5
6
7
8
9
10
11
<body>
    <script src="module-a.js"></script>
    <script>
        moduleA.method1()
        console.log(moduleA.name) // undefined
    </script>
</body>
1
2
3
4
5
6
7

# 解决的问题

  • 实现了私有成员的概念。内部成员只能通过闭包的形式访问,在外部是无法访问的,这样确保了私有成员的安全
  • 我们可以用自执行函数的参数作为我们依赖声明 去使用,让模块的依赖关系变得明显了。
// 定义的时候接受一个jquery的参数
;(function ($) {
    var name = 'module-a'
    
    function method1 () {
        console.log( name + 'use method1')
        $('body').animate({ margin: '200px' })
    }
    
    window.moduleA = {
        method1: method1
    }
// 嗲用的时候传入jQuery,我们就清楚的知道这个模块依赖jQuery    
})(jQuery)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

前三种都是早期在没有工具和规范的情况下以原始的模块系统为基础,通过约定的方式去实现模块化的代码组织,这些方式确实解决了在前端没有模块化的一些问题。但是仍然存在其他没有解决的问题

# 还存在这些没有解决的问题

  • 在不同的实施者实行的时候会有差别,并没有一个统一的标准去规范模块化的实现方式。
  • 依然是利用html引入script标签的方式手动进行加载,意味着模块加载并不受代码的控制,不利于维护,所以我们需要通过代码自动的帮我们加载模块。
<body>
    <!--如果有的代码中需要引入jquery但是忘记引用,
    或者不需要引用jquery但是忘记删除,
    有时候代码多了哪些需要引用哪些不需要引用,就会造成维护成本-->
    <script src="https://unpkg.com/jquery"></script>
    <script src="module-a.js"></script>
    <script src="module-b.js"></script>
</body>
1
2
3
4
5
6
7
8

# Stage4 —— 模块化规范出现

我们需要模块化标准和模块化加载的基础库(模块化标准 + 模块加载器)。

  • CommonJS规范: nodeJS中提出的标准,在node.js中所有的代码必须遵循CommonJS规范。
  • AMD规范: 浏览器端根据自身特性结合CommonJS使用了AMD规范(异步模块定义规范),还推出了新的库require.js实现了AMD规范。
  • CMD规范: 淘宝实现的一个规范(通用模块定义规范),对应的库是Sea.js,类似CommonJS规范。其目的是写出来的代码尽可能和CommonJS类似,减少学习成本,这种方式最后被require.js兼容。

# Stage5 —— 模块化标准规范(目前模块化的最佳实践)

在node.js中遵循CommonJS模块,在浏览器中采用ES Modules的规范。

对于ES Modules是在ES6中新的模块化系统,最近几年才定义的标准,会有很多兼容的问题。一开始推出的时候很多浏览器都不支持,随着webpack等打包工具的普及,这些模块化系统才开始普及。

而目前ES Modules是最主流的前端模块化方案,是用语言层面实现了模块化。很多浏览器很对这部分进行了兼容,未来有很大的发展。也是我们需要学习的重点。

更新时间: 2021-10-11 15:57