模块化发展

什么是模块?

模块化是一种解决问题的方案,一个模块就是实现某种特定功能的文件,可以帮助开发者拆分和组织代码。

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

    js模块化


JavaScript语言在设计之初只是为了完成简单的功能,因此没有模块化的设计。但是随着前端应用的越来越庞大,模块化成为了js语言必须解决的问题。

模块化发展

js的模块化发展大致可以划分为五个阶段:

  • 1、文件划分全局function模式 : 将不同的功能封装成不同的全局函数

    文件划分方式无法管理模块的依赖关系(不是强制定义模块依赖),而且模块内所有变量都挂载在全局对象上,容易污染全局作用域,命名冲突

按照js文件划分模块,一个文件可以认为是一个模块,然后将文件通过script标签的方式引入。
编写模块:foo.js

1
2
3
4
var foo = 'foo'
function sayHello() {
console.log(foo)
}

使用模块:

1
2
3
4
5
6
7
8
9
10
11
<html>
<header></header>
<body>
<!--先引用-->
<script src="./foo.js"></script>
<script>
// 通过全局对象调用
window.sayHello()
</script>
</body>
</html>
  • 2、命名空间namespace模式 : 简单对象封装

    使用命名空间的好处是可以尽量避免命名冲突,但是由于命名空间挂载在全局对象下,依然能够在外部修改模块的变量(没有实现模块私有化)。
    将文件内所有的变量都添加到一个命名空间下。
    编写模块:

    1
    2
    3
    4
    5
    6
    var FooModule = {
    foo: 'foo',
    sayHello() {
    console.log(FooModule.foo)
    }
    }

    使用模块:

    1
    2
    3
    4
    <script>
    // 通过命名空间调用
    FooModule.sayHello()
    </script>
  • 3、立即执行函数IIFE模式:匿名函数自调用(闭包)

    作用: 数据是私有的, 外部只能通过暴露的方法操作
    编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口
    问题: 如果当前这个模块依赖另一个模块怎么办?

编写模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(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)

使用模块:

1
2
3
4
<script>
// 通过命名空间调用
window.foo()
</script>
  • 4、IIFE模式增强 : 引入依赖

    这就是现代模块实现的基石

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 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)

    // index.html文件

    1
    2
    3
    4
    5
    6
    7

    <!-- 引入的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>

    模块化的好处

  • 避免命名冲突(减少命名空间污染)

  • 更好的分离, 按需加载

  • 更高复用性

  • 高可维护性

引入多个script后出现出现问题

  • 请求过多

  • 首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多

  • 依赖模糊

  • 我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。

  • 难以维护

以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。
模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上这些问题。而这些问题可以通过模块化规范来解决,下面介绍开发中最流行的commonjs, AMD, ES6, CMD规范。

  • 5、模块化规范

    ES2015提出了标准模块化规范,即ES Modules。它包含一个模块化标准和一个模块加载器。

编写模块

// moduleA.js

1
export const foo = 'foo'

// moduleB.js

1
2
3
// 会自动从服务器下载moduleA.js文件
import { foo } from './moduleA.js'
console.log(foo)

使用模块

1
2
3
4
5
6
7
<html>
<header></header>
<body>
<!--引入moduleB.js-->
<script type="module" src="./moduleB.js"></script>
</body>
</html>

注意事项:
引入模块js时,必须添加type=module
由于模块会自动下载依赖文件,因此html文件必须挂载到服务器下,直接文件浏览会报错。