AMD(Asynchronous Module Definition)
异步模块定义,所谓异步是指模块和模块的依赖可以被异步加载,他们的加载不会影响它后面语句的运行。有效避免了采用同步加载方式中导致的页面假死现象。AMD代表:RequireJS。
AMD一开始是CommonJS规范中的一个草案,全称是Asynchronous Module Definition,即异步模块加载机制。后来由该草案的作者以RequireJS实现了AMD规范,所以一般说AMD也是指RequireJS。
RequireJS是一个工具库,主要用于客户端的模块管理。它的模块管理遵守AMD规范,RequireJS的基本思想是,通过define方法,将代码定义为模块;通过require方法,实现代码的模块加载。
它主要有两个接口:define 和 require。define 是模块开发者关注的方法,而 require 则是模块使用者关注的方法。
define() 函数:
define(id?, dependencies?, factory); //id :可选参数,它指的是模块的名字。 //dependencies:可选参数,定义中模块所依赖模块的数组。 //factory:模块初始化要执行的函数或对象
需要注意的是,dependencies有多少个元素,factory就有多少个传参,位置一一对应。
使用栗子:
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) { exports.verb = function() { return beta.verb(); //Or: //return require("beta").verb(); } });
require() 函数
require([module], callback); //module:一个数组,里面的成员就是要加载的模块. //callback:模块加载成功之后的回调函数。
需要注意的是 ,module 有多少个元素,callback 就有多少个传参,位置一一对应。
require(["a","b","c"],function(a,b,c){ //code here });
AMD的优缺点
AMD 运行时核心思想是「Early Executing」,也就是提前执行依赖 AMD 的这个特性有好有坏:
- 首先,尽早执行依赖可以尽早发现错误。
- 另外,尽早执行依赖通常可以带来更好的用户体验,也容易产生浪费。
- 引用AMD的Javscript库: 目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js
- 在浏览器环境中异步加载模块;并行加载多个模块;
- 开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;不符合通用的模块化思维方式,是一种妥协的实现。
AMD在浏览器端的实现步骤
1. 下载require.js, 并引入
然后将require.js导入项目: js/libs/require.js
2. 创建项目结构
|-js |-libs |-require.js |-modules |-alerter.js |-dataService.js |-main.js |-index.html
3. 定义require.js的模块
// dataService.js文件 // 定义没有依赖的模块 define(function() { let msg = 'www.baidu.com' function getMsg() { return msg.toUpperCase() } return { getMsg } // 暴露模块 }); //alerter.js文件 // 定义有依赖的模块 define(['dataService'], function(dataService) { let name = 'Tom' function showMsg() { alert(dataService.getMsg() + ', ' + name) } // 暴露模块 return { showMsg } }); // main.js文件 (function() { require.config({ baseUrl: 'js/', //基本路径 出发点在根目录下 paths: { //映射: 模块标识名: 路径 alerter: './modules/alerter', //此处不能写成alerter.js,会报错 dataService: './modules/dataService' } }); require(['alerter'], function(alerter) { alerter.showMsg() }); })(); // index.html文件 <!DOCTYPE html> <html> <head> <title>Modular Demo</title> </head> <body> <!-- 引入require.js并指定js主文件的入口 --> <script data-main="js/main" src="js/libs/require.js"></script> </body> </html>
4. 页面引入require.js模块:
在index.html引入 <script data-main="js/main" src="js/libs/require.js"></script>
此外在项目中如何引入第三方库?只需在上面代码的基础稍作修改:
// alerter.js文件 define(['dataService', 'jquery'], function(dataService, $) { let name = 'Tom' function showMsg() { alert(dataService.getMsg() + ', ' + name) } $('body').css('background', 'green') // 暴露模块 return { showMsg } }); // main.js文件 (function() { require.config({ baseUrl: 'js/', //基本路径 出发点在根目录下 paths: { //自定义模块 alerter: './modules/alerter', //此处不能写成alerter.js,会报错 dataService: './modules/dataService', // 第三方库模块 jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错 } }) require(['alerter'], function(alerter) { alerter.showMsg() }) })()
上例是在alerter.js文件中引入jQuery第三方库,main.js文件也要有相应的路径配置。
小结:通过两者的比较,可以得出AMD模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。AMD模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。
UMD规范
CMD(Common Module Definition)
CMD是SeaJS在推广过程中生产的对模块定义的规范,在Web浏览器端的模块加载器中,SeaJS与RequireJS并称,SeaJS作者为阿里的玉伯。
CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。
CMD语法
定义暴露模块:
//定义没有依赖的模块 define(function(require, exports, module){ exports.xxx = value module.exports = value }) //定义有依赖的模块 define(function(require, exports, module){ //引入依赖模块(同步) var module2 = require('./module2') //引入依赖模块(异步) require.async('./module3', function (m3) { }) //暴露模块 exports.xxx = value })
引入使用模块:
define(function (require) { var m1 = require('./module1') var m4 = require('./module4') m1.show() m4.show() })
CMD的优缺点
- 优点:依赖就近,延迟执行 可以很容易在 Node.js 中运行;
- 缺点:依赖 SPM 打包,模块的加载逻辑偏重;
sea.js使用步骤
1. 下载sea.js, 并引入
- 官网: seajs.org/
- github : github.com/seajs/seajs
然后将sea.js导入项目: js/libs/sea.js
2. 创建项目结构
|-js |-libs |-sea.js |-modules |-module1.js |-module2.js |-module3.js |-module4.js |-main.js |-index.html
3. 定义sea.js的模块代码
// module1.js文件 define(function (require, exports, module) { //内部变量数据 var data = 'atguigu.com' //内部函数 function show() { console.log('module1 show() ' + data) } //向外暴露 exports.show = show }); // module2.js文件 define(function (require, exports, module) { module.exports = { msg: 'I Will Back' } }); // module3.js文件 define(function(require, exports, module) { const API_KEY = 'abc123' exports.API_KEY = API_KEY }); // module4.js文件 define(function (require, exports, module) { //引入依赖模块(同步) var module2 = require('./module2') function show() { console.log('module4 show() ' + module2.msg) } exports.show = show //引入依赖模块(异步) require.async('./module3', function (m3) { console.log('异步引入依赖模块3 ' + m3.API_KEY) }) }); // main.js文件 define(function (require) { var m1 = require('./module1') var m4 = require('./module4') m1.show() m4.show() })
4. 在index.html中引入
<script type="text/javascript" src="js/libs/sea.js"></script> <script type="text/javascript"> seajs.use('./js/modules/main') </script>
ES6模块化(==重点介绍==)
ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。所以说ES6是编译时加载,不同于CommonJS的运行时加载(实际加载的是一整个对象),ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
。
严格模式主要有以下限制。
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with
语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化- 不能使用
arguments.callee
- 不能使用
arguments.caller
- 禁止
this
指向全局对象 - 不能使用
fn.caller
和fn.arguments
获取函数调用的堆栈 - 增加了保留字(比如
protected
、static
和interface
)
上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6,所以请参阅相关 ES5 书籍,本书不再详细介绍了。
其中,尤其需要注意this
的限制。ES6 模块之中,顶层的this
指向undefined
,即不应该在顶层代码使用this
。
语法
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
export
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。
function cUl(){ let ulEle = document.createElement("ul"); for(let i = 0; i < 5; i++){ let liEle = document.createElement("li"); liEle.innerHTML = "无序列表" + i; ulEle.appendChild(liEle); } return ulEle; } let ul = cUl(); export {ul};
import
使用export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过import
命令加载这个模块。
import {table} from "../test/test_table.js"; import {div} from "../test/test_div.js" ; import {ul} from "../test/test_ul.js" ; export {table, div, ul};
ES6 模块与 CommonJS 模块的差异
它们有两个重大差异:
1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
下面重点解释第一个差异,我们还是举上面那个CommonJS模块的加载机制例子:
// lib.js export let counter = 3; export function incCounter() { counter++; } // main.js import { counter, incCounter } from './lib'; console.log(counter); // 3 incCounter(); console.log(counter); // 4
ES6 模块的运行机制与 CommonJS 不一样。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
学习有趣的知识,结识有趣的朋友,塑造有趣的灵魂!