commonJS、AMD、ES6模块使用规范
在早期的时候,只有浏览器环境,JavaScript 代码比较习惯于通过<script></script>
标记来实现的,不管是内联还是外联。这是我开始学 JavaScript 时的做法。我相信大多数 JavaScript 开发者在其生命里至少这样做过一次。
这是开始的好方法。不需要操心外部文件或者依赖。但是这也导致了不可维护的代码,因为:
- 缺乏依赖解析:你必须保证 main 函数之前就有 add、reduce 和 sum 函数。
- 命名空间污染:所有的函数的变量将都驻留在全局作用域。
##commonJS:
在 2009 年,出现了关于将 JavaScript 带到服务器端的讨论,因而 ServerJS 诞生了。之后,ServerJS 更名为 CommonJS。
CommonJS 并非一个 JavaScript 库,而是一个标准化组织,像 ECMA 或者 W3C 一样。ECMA 定义了 JavaScript 语言规范。W3C 定义了 JavaScript Web API,比如 DOM 和 DOM 事件。CommonJS 的目标是为 Web 服务器、桌面和命令行应用程序定义一套通用的 API。
CommonJS 还定义了模块 API 。因为在服务器应用程序中没有 HTML 页面,也没有 script 标记,所以就得有一些清晰的模块 API。模块需要暴露(export)给其它模块使用,并且是可访问的(import)。
这就是:
同步模块加载 NodeJS,也就是服务器端广泛使用的一种模块化机制,因为模块一般都存在于本地,不需要考虑网络加载因素,所以为同步加载。
commonJS一般在node.js中使用,规范将每一个js文件看成是一个模块,所以,nodejs会为每一个js文件生成一个module对象,这个module对象会有一个exports属性,并且这个exports属性是一个空对象,即module={ exports:{} },我们这行代码,是给了module对象中export属性赋值的过程)这样即可将config这个变量作为一个公共的输出,在b.js中如果我们想要用a.js中config这个变量,我们可以在b.js中用下面的代码来引用变config:var config=require(‘路径/a.js’).config.
模块的定义
每一个js文件看成是一个模块,每个模块都有自己独立的作用域,不同模块间变量互不影响。每个js文件中的module变量代表当前模块。
###导出
导出时,要求模块化的导出必须是module.exports,这样导出对外的变量或者接口。
a.js
var config = 10;
moduel.exportds = {config};
###使用
通过require() 来导入想要使用的其他模块对外导出的变量或者接口;
b.js
var aModuel = require('路径/a.js');
var aConfig = aModuel.config;
console.log(aConfig);//10
require的引入分为三种:
- 如果参数字符串以
/
开头,则表示加载的是一个位于绝对路径的模块文件。 - 如果参数字符串以
./
或者../
开头,则表示加载的是一个位于相对路径的模块文件 - 如果参数字符串不以
./
或/
开头,则表示加载的是一个默认提供的核心模块(node核心模块,或者通过全局安装或局部安装在node_modules目录中的模块)
##AMD:
CommonJS 风格的模块定义的问题是,它不是异步的。当调用var add=require('add');
时,系统会暂停,直到模块准备好了。这意味着在所有模块正在加载时,这行代码会冻结浏览器。所以这可能不是定义浏览器端模块的最佳方式。 为了把服务器端用的模块语法转换给浏览器端用,CommonJS 提出了几种模块格式。其中之一,即 “Module/Transfer/C”,后来成为异步模块定义(AMD)。
异步模块定义 ,为浏览器环境设计,RequireJS即为遵循AMD规范的模块化工具,requireJS的基本思想是,通过define方法定义模块化,通过require加载模块。使用时要先下载并引入require.js文件。
模块的定义和使用:
require.js的加载
第一步,去官网下载最新版本,直接放到页面进行加载
<script src="js/require.js"></script>
加载这个文件可能会导致网页失去响应,可以将它放到页面的底部加载,也可以这样写
<script src="js/require.js" defer async="true" ></script>
async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上。
加载require.js以后,下一步就要加载我们自己的代码了,也就是入口,可以叫主模块,如果文件名叫main.js,写成下面这样就可以了:
<script src="js/require.js" data-main="js/main"></script> .js后缀可以省略
####主模块
//如果主模块依赖于jQuery可以这样写
require(['jquery'], function ($){
alert($);
});
####require.config()方法
require.config({
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});
上面的代码给出了三个模块的文件名,路径默认与main.js在同一个目录(js子目录)。如果这些模块在其他目录,比如js/lib目录,则有两种写法。一种是逐一指定路径。
require.config({
paths: {
"jquery": "**lib/**jquery.min",
"underscore": "**lib/**underscore.min",
"backbone": "**lib/**backbone.min"
}
});
另一种则是直接改变基目录(baseUrl)。
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});
如果某个模块在另一台主机上,也可以直接指定它的网址,比如
```js
require.config({
paths: {
“jquery”: “https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min”
}
});
```
define(['module1','module2'], function(module1, module2) {//其中module1,module2为顺序依赖项
...
return {
// 返回的接口
}
})
例如:
define(["aa"],function(aa) {//依赖aa并向外导出config
var config = 10 + aa
return {
config: config
}
})
AMD模块的写法
require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。
具体来说,就是模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。
// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
加载方法如下:
// main.js
require(['math'], function (math){
alert(math.add(1,1));
});
如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。
define(['myLib'], function(myLib){
function foo(){
myLib.doSomething();
}
return { //返回模块中的函数
foo : foo
};
});
当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。
##ES6模块
JavaScript 全局模块对象、CommonJS、AMD 和 UMD,太多选择了。现在也许你会问,下一个项目我该用哪一个呢?答案是一个都不用。
JavaScript 语言中并没有内置模块系统。这正是我们有如此多输入和输出模块的不同方式的原因。但是这种情况最近得到改变了。在 ES 6 规范中,模块已经成为 JavaScript 的一部分。所以这个问题的答案是,如果想让项目不会过时,就得用 ES 6 模块语法。
如果使用es6语法,那么则无需引入requireJS进行模块化,它的特点主要为:
- 在模块顶级作用域中的this为undefine。
- 单个文件为一个模块,顶级作用域声明的变量只在当前模块生效。对其他模块不影响,
- 对外导出的变量才能被其他变量使用
- ES6 的模块自动采用严格模式,不管有没有在模块头部加上"use strict";。
定义
导出内容有两种关键字:
- export 导出该模块要导出的变量、函数、对象等等。
export const color = '#fff';
- as 输出时创建别名,也适用于导入情况。
const color = '#fff';
export color as white
- export default 该模块的默认输出值,可以为变量、函数、对象,一个模块只能导出一个默认值。默认导出的内容可以无名称,因为默认导出就代表该模块,但也可以有名称,或者使用别名 as。
export default const color = '#fff';
// export default 5;
// const color = ‘#fff’;
// export { color as default };
使用
在模块中使用import关键字来导出其他模块导出的内容。
分为几种情况:
- 导入非默认内容,需要用结构的方式,因为在模块中,非默认导出的内容,都会被添加到一个变量中,用解构的方式拿出一个或多个内容。
import { color } from './color';
- 导入默认内容,可以直接导出即可。
import color from './color';