CommonJS中require
的功能是加载并执行一个js文件,并返回该文件的exports
对象,是同步的。由于在服务器端加载并执行一个文件的时间是可以忽略的,所以可以在运行时加载,是同步的。但是在浏览器端,如果每加载一个文件都要发请求,在网速慢的情况下得移植等请求返回,浏览器就会卡死,阻塞后面代码的运行。
为了解决这个问题,后面发展起来了众多的前端模块化规范,包括 CommonJS 大致有如下几种:
AMD (Asynchronous Module Definition)
RequireJS 是一个 js 文件和模块加载器。它非常适合在浏览器中使用,但它也可以用在其他 js 环境, 就像 Rhino 和 Node。 使用 RequireJS 加载模块化脚本能提高代码的加载速度和质量。
它解决了 CommonJS 规范不能用于浏览器端的问题,而 AMD 就是 RequireJS 在推广过程中对模块定义的规范化产出。
来看看 AMD 规范的实现:
<script src="require.js"></script>
<script src="a.js"></script>
首先要在 html 文件中引入 require.js 工具库,就是这个库提供了定义模块、加载模块等功能。它提供了一个全局的 define 函数用来定义模块。所以在引入 require.js 文件后,再引入的其它文件,都可以使用 define 来定义模块。
define(id?, dependencies?, factory)
id 可选参数,模块标识。没有就使用js文件名。如果一个 js 文件只定义了一个模块时,这个参数是可以省略的;
dependencies 可选参数,模块的所有依赖。没有可以不写;
factory 工厂函数,如果是函数只调用一次,返回模块要导出的值。如果是对象则为模块导出的值。
所以模块A可以这么定义
// a.js
define(function(){
var name = 'morrain'
var age = 18
return {
name,
getAge: () => age
}
})
// b.js
define(['a.js'], function(a){
var name = 'lilei'
var age = 15
console.log(a.name) // 'morrain'
console.log(a.getAge()) // 18
return {
name,
getAge: () => age
}
})
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在回调函数中,等到加载完成之后,这个回调函数才会运行。
RequireJS 的基本思想是,通过 define 方法,将代码定义为模块。当这个模块被 require 时,它开始加载它依赖的模块,当所有依赖的模块加载完成后,开始执行回调函数,返回值是该模块导出的值。
CMD (Common Module Definition)
和 AMD 类似,CMD 是 Sea.js 在推广过程中对模块定义的规范化产出。Sea.js 是阿里的玉伯写的。它的诞生在 RequireJS 之后,玉伯觉得 AMD 规范是异步的,模块的组织形式不够自然和直观。于是他在追求能像 CommonJS 那样的书写形式。于是就有了 CMD 。
来看看 CMD 规范的实现:
<script src="sea.js"></script>
<script src="a.js"></script>
首先要在 html 文件中引入 sea.js 工具库,就是这个库提供了定义模块、加载模块等功能。它提供了一个全局的 define 函数用来定义模块。所以在引入 sea.js 文件后,再引入的其它文件,都可以使用 define 来定义模块。
// 所有模块都通过 define 来定义
define(function(require, exports, module) {
// 通过 require 引入依赖
var a = require('xxx')
var b = require('yyy')
// 通过 exports 对外提供接口
exports.doSomething = ...
// 或者通过 module.exports 提供整个接口
module.exports = ...
})
// a.js
define(function(require, exports, module){
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = () => age
})
// b.js
define(function(require, exports, module){
var name = 'lilei'
var age = 15
var a = require('a.js')
console.log(a.name) // 'morrain'
console.log(a.getAge()) //18
exports.name = name
exports.getAge = () => age
})
Sea.js 可以像 CommonsJS 那样同步的形式书写模块代码的秘诀在于: 当 b.js 模块被 require 时,b.js 加载后,Sea.js 会扫描 b.js 的代码,找到 require 这个关键字,提取所有的依赖项,然后加载,等到依赖的所有模块加载完成后,执行回调函数,此时再执行到 require('a.js') 这行代码时,a.js 已经加载好在内存中了.
ES6 Module
前面提到的 CommonJS 是服务于服务端的,而 AMD、CMD 是服务于浏览器端的,但它们都有一个共同点:都在代码运行后才能确定导出的内容。
从 ES6 开始,在语言标准的层面上,实现了模块化功能,而且实现得相当简单,完全可以取代 CommonJS 和 CMD、AMD 规范,成为浏览器和服务器通用的模块解决方案。