js 是一种脚本语言,与 PHP 类似。但是如今 PHP 不再仅仅构建一个简单的web 系统,它也在向 Java 看齐,也能够支撑起一个庞大的 web 系统。国外非常有名的脸书网就是一个典型的例子。一门语言,要构建出庞大复杂的系统的基础之一就是语言文件的相互引用。毕竟一个单文件直接实现一个庞大复杂的系统是十分吃力的。
后端模块化
CommonJs
在互联网早期,JavaScript 一直在前端发光发热,但在后端基本上没有什么建树。并且 JavaScript 官方的一些规范也仅仅只是对于前端做了很多要求,没有考虑到后端,这样导致 JavaScript 一直寄生于浏览器中。但是 JavaScript 社区不满足仅仅让 JavaScript 这门语言只存在于浏览器里,他们希望能够在任何地方都能运行 JavaScript。于是,CommonJs 应运而生,它是社区制定的一份 JavaScript 规范。
CommonJs 涵盖的内容
- 模块
- 二进制
- Buffer
- 字符集编码
- I/O流
- 进程环境
- 文件系统
- 套接字
- 单元测试
- Web服务器网关接口
- 包管理
- …
CommonJs 的模块规范
CommonJs 的模块规范很简单,主要包含三部分,模块定义,模块标识以及模块引用。
模块引用
示例如下:
var math = require('math');
主要使用了 require()
方法,他接受一个参数,模块标识。
模块定义
示例如下:
exports.add = function (a, b) {
return a + b;
};
主要使用了 exports 对象,只要将方法挂载在 exports 对象上作为属性即可定义导出的方式。
下面给出一个模块引入及使用的例子:
var Math = require('math');
exports.getAll = function (x, y) {
return Math.add(x, y);
};
模块标识
模块标识就是传递给 require()
方法的参数,他要求是符合小驼峰命名的字符串,或者以.
,..
开头的相对路径,或者绝对路径。他可以没有文件后缀名js。
CommonJs 与 Node.js
CommonJs 只是定义了一份模块的规范,而 Node.js 根据这份规范实现了模块机制。这好比接口和实现类,一个给出了接口,另一个真正实现了接口,但是实现可能有很多种不同的方式。
前端模块化
说完后端模块化,我们再来聊一聊前端模块化。
如何去实现前端的模块化呢?
有人可能会说,这还不简单,既然后端模块化已经可以良好的工作了,那我们直接照搬到前端不就可以了嘛!
理论上是的,但是现实中却遇到了一个大问题。我们要知道在后端,文件的 I/O 很快,因为我们直接在服务器的硬盘上读写文件即可。但是在前端可不是这个样子,所有文件的 I/O 都是要经过 http 请求去完成的,网络延时是个大问题。
如果一个页面空白 3 秒钟以上,那么用户肯定会不爽,一般他们会刷新,如果刷新页面几次都不能看到页面,那可能会直接关掉页面,甚至以后都不会再次访问你的站点。
所以,在前端我们实现模块化不能仿照后端那样,进行同步加载,最好的方式就是异步加载。而在前端比较火的两个异步加载规范就是 AMD 和 CMD。
AMD
- 全称:Asynchronous Module Definition,异步模块定义
- AMD 是 RequireJS 在推广过程中对模块定义的规范化产出
在 AMD 规范中,一个模块就是一个文件。该规范的核心就是define()
函数,define()
是一个全局函数,用来定义模块。
define(id?, dependencies?, factory);
下面给出一个示例:
define("types/Manager", ["types/Employee"], function (Employee) {
function Manager () {
this.reports = [];
}
//开始执行
Manager.prototype = new Employee();
//返回经理构造函数可以由其他模块的应用。
return Manager;
}
);
CMD
- 全称:Common Module Definition,公共模块加载
- CMD 是 SeaJS 在推广过程中对模块定义的规范化产出
在 CMD 规范中,一个模块就是一个文件。该规范的核心就是 define()
函数,define()
是一个全局函数,用来定义模块。
define(function(require, exports, module) {
// 模块代码
});
下面给出一个示例:
define(function(require, exports, module) {
// 通过 require 引入依赖 注意 .js 可以省略
var $ = require('jquery');
// 你也可以引入自己的函数依赖
var Spinning = require('./yourFunction');
var util = {};
util.sayHello = function(){
return 'seajs向你问好';
}
// 通过 exports 对外提供接口
module.exports = util;
});
AMD 和 CMD 的区别
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible。
- CMD 推崇依赖就近,AMD 推崇依赖前置。
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b')
// 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) {
// 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
ES6
ES6 这个词的原意,就是指 JavaScript 语言的下一个版本,他由国际标准化组织 ECMA 提出。ES6 模块是编译时加载,而 CommonJS 模块是运行时加载。
ES6 模块功能主要由两个命令构成:export 和 import。
- export 命令用于规定模块的对外接口
- import 命令用于输入其他模块提供的功能
export 命令
- 输出变量
export var firstName = 'Michael';
- 输出函数
export function multiply(x, y) {
return x * y;
};
- 使用大括号输出的一组变量
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
function f() {}
export {firstName, lastName, year, f};
- 使用 as 关键字重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
import 命令
import 命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
- 引入变量
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
- 使用 as 关键字重命名
import { lastName as surname } from './profile';
- 执行加载模块
import 'lodash';
- 整体加载
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
- export default 命令
export default function () {
console.log('foo');
}
其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字。
import customName from './export-default';
customName();