前端模块化

前段模块化基础

  • 概念

    前端模块化其实就是将一个完整的单一的功能整合起来形成单独的一个功能组件,当需要用的的时候只需要加载这个组件,然后便可以通过该组件唯一的名称去使用其中的内容。

  • 主流模块化框架

    • commonJS
    • AMD
    • CMD
    • UMD
    • ES6规范

commonJS

  • commonJS模块化

    • 定义模块:即一个单独的文件就是一个模块,切该文件中的作用域独立,当中的变量是无法被其他文件引用的,如果要使用需要将其定义为global
    • 输出模块:模块只有一个出口,即使用module.exports对象,将需要输出的内容放入到该对象中;
    • 加载模块:通过require加载,例如:var module = require('./moduleFile.js');该module的值即对应文件内部的module.exports对象, 然后就可以通过module名称去引用模块中的变量和函数了;

      例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      // 定义模块 module.js
      var data = "hello commonJS !";

      function test1() {
      alert("hello test1 !");
      }

      function test2() {
      alert("hello test2 !");
      }

      // 输出模块
      module.exports = {
      test1: test1,
      test2: test2
      }

      // 加载模块
      var module = require('./module.js');

      // 使用模块功能
      module.test1(); // "hello test1 !"
      module.test2(); // "hello test2 !"

      总共四个步骤: 定义 -> 输出 -> 加载 -> 使用

      PS: commonJS是基于服务器端开发的,由于require是同步加载,所以当在浏览器中是无法运行的,所以就有了下面的异步模块的出现。

AMD(Asynchronous Module Definition),异步模块定义

  • AMD是一套基于浏览器端模块化开发的规范,在进行页面开发时需要用到该规范的库函数,即:requireJS

  • requireJS解决了两个问题

    • 多文件依赖关系处理,被依赖的需要早一步被加载;
    • 加载js时候页面会停止渲染,如果加载的文件越多,页面失去响应的时间就会越长;
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 模块定义 module.js
define(
['dependModule', '...', ...], // 这个数组表示该模块所依赖的模块名称
function () {
var data = "hello AMD !";
function test1() {
alert("hello test1 !);
}

function test2() {
alert("hello test2 !);
}


return {
test1: test1,
test2: test2
};
});


// 加载模块
require(['module'], function (myModule) {
// 加载之后的module模块将会以参数形式:myModule传入到回调函数中,供使用
// 这里是模块加载完成之后的回调函数
myModule.test1(); // "hello test1 !"
myModule.test2(); // "hello test2 !"
});

// ---------- AMD语法:
1. 模块定义:使用全局函数`define(id, dependencies, factory);`
1. id: 用来定义模块标识,非必选参数,如果没提供该参数则会默认使用脚本文件名(不带扩展名的);
2. dependencies:是表示该模块所依赖的模块名称数组;
3. factory:如果是函数则为该模块初始化所执行的函数,如果是对象则为该模块的输出值。
4. 模块加载:使用**异步的**`require(dependencies, function (myModule) {});`函数
1. `dependencies`: 模块依赖的模块数组;
2. 回调函数:模块加载完成后执行的函数,加载的模块将以参数形式传入该函数,即:myModule,供使用。

CMD(Common Module Definition)通用模块定义

  • 就近依赖,需要时再进行加载,所以执行顺序和书写顺序一致;这点与AMD不同,AMD是在使用模块之前将依赖模块全部加载完成,但由于网络等其他因素可能导致依赖模块下载先后顺序不一致这就导致了,执行顺序可能跟书写顺序不一致的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义模块 module.js
define(function (require, exports, module) {

// 在该函数里面通过require实时的加载依赖模块
var module1 = require('./module1.js');

module1.test1();
});

// 加载模块
seajs.use(['module.js'], function (myModule) {
// 除了用的是seajs.use其他和AMD的加载方式类似
});

ES6模块化,未来标准,但目前未广泛使用

  • 优点

    • 类似commonJS,语法更简洁;
    • 类似AMD,直接支持异步加载和配置模块加载;
    • 结构可以做静态分析,静态检测;
    • 比commonJS更好的支持循环依赖;
  • 语法概述

    • 命名式导出方式:每个模块可以有多个
    • 定义式导出方式:每个模块只有一个
ES6命名式导出方式

一个模块通过export声明来导出多个,需要导出的变量或函数只需要在声明最前面加上export关键词即可,然后在需要用的地方使用import导入。这些导出主要根据变量和函数名字来区分,故称之为命名式导出方式。

例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// module.js
export const PI = 3.1415926;
export function double( r ) {
return 2 * r;
}

export function sum( x, y ) {
return x + y;
}

// 在main.js中使用
import { PI, double, sum } from 'module';

console.log(PI); // 3.1415926
console.log(double(10)); // 20
console.log(sum(10, 8)); // 18

// 使用属性命名来使用模块
import { PI, double, sum } as myModule from 'module';
// 这样就可以通过 myModule.double(10);语法去调用模块内变量或函数了
console.log( myModule.PI ); // 3.1415926
console.log( myModule.double(10) ); // 20
console.log( myModule.sum(10, 8) ); // 18

上面是ES6标准方式,下面是commonJS形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// module.js
var pi = 3.1415926;
function double( r ) {}
function sum( a, b ) {}

module.exports = {
pi: pi,
double: double,
sum: sum
};

// main.js
var module = require('./module.js');

console.log( module.pi );
module.double(10);
module.sum(10, 8);

从上述代码中可知命名式导出方式的使用步骤

  1. 在模块文件中使用export关键字将指定的变量或函数导出;
  2. 然后在需要使用模块内函数或变量的地方使用import从该模块文件中导入这些变量或函数;
ES6定义式导出方式
  • 概念:定义式导出理解上就是一个文件一个模块,一个模块一个功能,不像命名式一样可以一个文件可以导出多个变量或函数(事实上命名式和定义式可以结合使用,看下面例子)。

  • 定义方式:

    单函数定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // myFunc.js
    export default function () {
    // 这里面包含了该模块(或者说该文件myFunc.js)所有逻辑
    // 这种方式该函数不需要名字,因为前面的default关键字意思即导出时使用的模块名字和文件名一样即:myFunc
    };

    // main.js
    import myFunc from 'myFunc';

    // 导入后就可以直接使用了
    myFunc();

    类的定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // MyClass.js(首字母大写)
    export default class () {
    // 类内容
    // class: 指定该模块导出的是一个类
    };

    // main.js
    var myclass = new MyClass();

    // 创建类之后就可以通过实例myclass去访问类中的成员和方法了
  • 命名式 + 定义式相结合使用

    命名式和定义式结合使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // module.js
    export default function () {};
    export function test() {};


    // main.js
    import { module, test } from 'module';

    // 另外定义式的导出还可以直接用'default'代替,甚至可以定义别名,如下:
    import module from 'module';
    import default from 'module';
    import { default as myModule } from 'module';
    三种方式最终结果是一样的,根据不同需求和习惯选择。

ES6导入和导出

  • ES6提供的几种导入方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 1. 定义式导出和命名式导出,以及相结合的导出
    import default from 'module'; // 定义式
    import { name1, name2 } from 'module'; // 命名式
    import defualt, { name1, name2 } from 'module'; // 结合式

    // 2. 导出重命名,即将模块导出后重命名为指定名字以供使用
    import { name1 as rename1, name2 } from 'module';// 即将name1重命名为rename1供使用

    // 3. 将整个模块一起导入,统一用指定属性名去调用模块内的内容
    import * as moduleName from 'module';

    // 4. 值加载不导出模块
    import 'module';
  • ES6提供的几种导出方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 在模块内部使用export导出
export var v1 = 10; // 全局变量
export let v2 = 11; // 局部变量
export const v3 = 12; // 常量

export function f1() {} // 函数
export class MyClass {} // 类

// 2. 模块内部使用default导出整个表达式
export default 123;
export default function (x) { return x; }
export default x => x;
export default class {
constructor(x, y) {
this.x = x;
this.y = y;
}
};

// 3. 将所有想导出的列出来放到文件最后一起导出
export { v1, v2, v3 };

// 4. 还可以别名导出
export { v1 as vv1, v2 as vv2, v3 };

// 5. 最后一种:重导出,即在一个模块里从另一个模块里导出内容
// module.js
export { v1, v2 } from 'otherModule';
// 别名
export { v1 as vv1, v2 } from 'otherModule';

ES6模块元数据,通过this module访问当前模块数据的方式

1
2
3
4
5
6
7
8
// main.js
// 访问module中的oData
import { oData } from this module;
console.log( oData );

// 如果想访问当前模块的oData,就需要
import * as currModule from this module;
console.log( currModule.oData );
0%