前端模块化-CommonJS,AMD和ES6模块规范

文章共1.5k个字 读完大约需要5分钟

模块化

前端最近几年发展迅速,javascript由早期的简单平面,发展到现在的多维度,原来的代码组织规范越来越难以驾驭大规模的项目,模块化开发被提上了台面。

模块化我理解的是任何一个功能,一个函数,一个.js文件,一个对象…都可以成为一个模块,模块化的思想是让所有的代码都有自己合适的位置,模块化的作用是拆分复杂为简单,让后期维护工作更为得心应手。

日常团队合作中,我们难以处理的是命名冲突项目依赖关系,而模块化开发就是封装所有,根据规范抛出接口与外界联系,彼此之间相互不影响,只暴露我们希望暴露的方法和数据。

CommonJS规范

CommonJS对模块进行了规范,它主要分为模块定义,模块引用和模块标识。根据这个规范,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

NodeJS的模块系统就遵循了CommonJS规范,但Node在实现中并非完全按照CommonJS规范实现,而是对模块规范进行了一定的取舍。下面,我们结合Node来深入了解CommonJS规范。

模块定义

1
2
3
4
5
6
7
8
9
10
11
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
this.filename = null;
this.loaded = false;
this.children = [];
};

module.exports = Module;
var module = new Module(filename, parent);

module代表当前模块,它以上的属性分别代表

  • module.id: 模块的识别符,通常是带有绝对路径的模块文件名。
  • module.filename: 模块的文件名,带有绝对路径。
  • module.loaded: 返回一个布尔值,表示模块是否已经完成加载。
  • module.parent: 返回一个对象,表示调用该模块的模块。
  • module.children: 返回一个数组,表示该模块要用到的其他模块。
  • module.exports: 初始值为一个空对象{},表示模块对外输出的接口。

模块引用

require方法用于加载模块,它有一个参数,即带有参数路径的模块的文件名或者为模块名

1
2
3
const user = require('./user'); //相对路径的模块名
const nav = require('/home/nav'); // 绝对路径的模块名
const http = require('http'); //模块名

模块标识

模块标识就是require函数的参数名称,一般要符合驼峰命名法,默认是寻找以.js结尾的文件

CommonJS是同步的,意味着你想调用模块里的方法,必须先用require加载模块。这对服务器端的Nodejs来说不是问题,因为模块的JS文件都在本地硬盘上,CPU的读取时间非常快,同步不是问题。但如果是浏览器环境,要从服务器加载模块。模块的加载将取决于网速,如果采用同步,网络情绪不稳定时,页面可能卡住,这就必须采用异步模式。所以,就有了AMD解决方案。

AMD规范

AMD规范是由CommonJS规范演变而来,大多数情况下和CommonJs规范一致,最大的区别是AMD加载模块是异步加载,所以,一般服务端用CommonJS,而浏览器端则遵循AMD

定义模块

1
2
3
4
5
6
define(id?, dependencies?, factory);

// amd属性可以判断当前文件加载是否遵循AMD规范,例如jquery的写法:
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function () { return jQuery; } );
}
  • 第一个参数,id(名字),是个字符串。它指的是定义中模块的名字,这个参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
  • 第二个参数,dependencies(依赖),是个定义中模块所依赖模块的数组。依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中。
  • 第三个参数,factory(工厂方法),为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。

引入模块

  • 第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:
1
2
3
4
5
6
require([module], callback);

// 加载模块实例
require(['a'], function (a) {
  a.FuncA();
});

ES6模块规范

ES6引入了新的模块规范,新的规范定义用export提供对外的接口,用import引入模块。ES6模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJSAMD模块,都只能在运行时确定这些东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ES6导出模块实例 1
export var firstName = 'Michael';
// ES6导出模块实例 2
var firstName = 'Michael';
export {firstName, lastName, year};
// ES6导出模块实例 3
function v1() { ... }
export {
v1 as streamV1,
};

// ES6导入模块实例 1
import {firstName, lastName, year} from './profile.js';
// ES6导入模块实例 2
import {a} from './xxx.js'
// ES6导入模块实例 3
import { lastName as surname } from './profile.js';

推荐文章

-->