amd,umd,commonJs,ES6模块的相关总结
AMD/CMD 模块(requireJS/seaJs)(即将退出历史舞台)
AMD
(Asynchronous Module Definition
异步模块定义)和CMD
(Common Module Definition
通用模块定义)是基于浏览器使用并且是异步执行
AMD
推崇依赖前置,在定义模块的时候就要声明其依赖的模块CMD
推崇就近依赖,只有在用到某个模块的时候再去require
javascript
// AMD
// 定义模块 myModule.js
define(['dependency'], function () {
var name = 'Byron';
function printName() {
console.log(name);
}
return {
printName: printName,
};
});
// 加载模块
require(['myModule'], function (my) {
my.printName();
});
// CMD
// 定义模块 myModule.js
define(function (require, exports, module) {
var $ = require('jquery.js');
$('div').addClass('active');
});
// 加载模块
seajs.use(['myModule.js'], function (my) {});
AMD
在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require
的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行。CMD
加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require
语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。
commonJS 模块(nodejs)
- 使用
require
来引入其他模块的代码,使用module.exports
来引出。 exports
与module.exports
的初始指针相同,即module.exports === exports
,如果exports
一旦指向了其他对象,即不能用于导出。- 运行时加载,输出的是一个值的拷贝。
值的拷贝
require
引入的是值的拷贝(基本类型拷贝值,引用类型拷贝地址)。
javascript
//4.js
var age = 0;
exports.age = age;
exports.getAge = () => {
age = age + 1;
};
setTimeout(() => {
console.log(age); //1 原本的会改变
}, 2000);
//5.js
const a = require('./4');
var age = a.age;
var getAge = a.getAge;
getAge();
console.log(age); //0 不会改变age,一旦生成缓存后就会从缓存里读这个值
commonjs 循环加载
javascript
//4.js
console.log('4开始执行');
setTimeout(() => {
exports.name = 'qxq1';
});
exports.name = 'qxq';
console.log('4执行一半');
const b = require('./5');
console.log('in 4, b.done =', b.done);
exports.done = true;
console.log('4执行结束');
javascript
//5.js
console.log('5开始执行');
exports.done = false;
const a = require('./4');
console.log('in 5, a.name =', a.name);
console.log('in 5, a.done =', a.done);
exports.done = true;
console.log('5执行结束');
setTimeout(() => {
console.log(666, a);
}, 5000);
javascript
//打印结果
4开始执行
4执行一半
5开始执行
in 5, a.name = qxq
in 5, a.done = undefined
5执行结束
in 4, b.done = true
4执行结束
666 Object {name: "qxq1", done: true}
- 执行到
require
那行才会去加载该脚本 require
命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象,本质就是一个一次性赋值操作。- 再次执行
require
命令,也不会再次执行该模块,而是到缓存之中取值。 - 一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
ES6 模块
import/export
命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,则会报错。ES6
模块输入是export
的动态只读视图(live read-only views)
import
使用 import
命令加载其他模块,import
命令输入的变量都是 只读
的,因为它的本质是 输入接口
。
语法查阅
javascript//默认导出的导入 import defaultExport from "module-name"; //整体导入 import * as name from "module-name"; //导入单个接口 import { export } from "module-name"; //重命名接口 import { export as alias } from "module-name"; // 导入多个接口 import { export1 , export2 } from "module-name"; import { foo , bar } from "module-name/path/to/specific/un-exported/file"; import { export1 , export2 as alias2 , [...] } from "module-name";个接口 //同时导入默认和多个接口 import defaultExport, { export [ , [...] ] } from "module-name"; import defaultExport, * as name from "module-name"; //只运行模块代码不导入接口 import "module-name";
如果多次重复执行同一句
import
语句,那么只会执行一次.import
是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
export
使用
export
命令规定对外的接口
,必须与模块内部的变量建立一一对应关系。export
语句输出的接口,与其对应的值是动态绑定
关系,即通过该接口,可以取到模块内部实时的值。语法查阅
javascript// 导出单个特性 export let name1, name2, …, nameN; // also var, const export let name1 = …, name2 = …, …, nameN; // also var, const export function FunctionName(){...} export class ClassName {...} // 导处列表 export { name1, name2, …, nameN }; // 重命名导出 export { variable1 as name1, variable2 as name2, …, nameN }; // 默认导出 export default expression; export default function (…) { … } // also class, function* export default function name1(…) { … } // also class, function* export { name1 as default, … }; // 复合导出 export * from …; export { name1, name2, …, nameN } from …; export { import1 as name1, import2 as name2, …, nameN } from …; export { default } from …;
默认导出
export default
javascript// 正确 export var a = 1; // 正确 export default 42; // 正确 var a = 1; export default a; // 正确 let k; export default k = 12; // 错误 export default var a = 1;
因为
export default
命令的本质是将后面的值,赋给default
变量,所以可以直接将一个值写在export default
之后。
值的引用
javascript
//4.js
import { age, getAge } from './5.js';
console.log(age); //原本为0
getAge();
console.log(age); //因为是值的引用,所以要变,为1
//5.js
export var age = 0;
export function getAge() {
age = age + 1;
}
setTimeout(() => {
console.log(age); //要变,为1
}, 2000);
ES6 循环加载
import
命令会被 JavaScript 引擎静态分析,具有提升效果,会提升到整个模块的头部,首先执行。export
命令会有变量声明提前的效果。ES6
模块遇到模块加载命令import
时,不会去执行模块,而是只生成一个引用
。等到真的需要用到时,再到模块里面去取值。ES6
根本不会关心是否发生了"循环加载",只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。- 通常存在强耦合,应避免出现。
例 1
javascript
// a.js
import { foo } from './b';
console.log('a.js');
export const bar = 1;
export const bar2 = () => {
console.log('bar2');
};
export function bar3() {
console.log('bar3');
}
// b.js
export let foo = 1;
import * as a from './a';
console.log(a);
// 注意函数表达式和函数声明的区别(提升)
例 2
javascript
// a.js
console.log('a starting');
import { foo } from './b';
console.log('in b, foo:', foo);
export var bar = 2;
console.log('a done');
// b.js
console.log('b starting');
import { bar } from './a';
export var foo = 'foo';
console.log('in a, bar:', bar);
setTimeout(() => {
console.log('in a, setTimeout bar:', bar);
});
console.log('b done');
// babel-node a.js
// 执行结果:
// b starting
// in a, bar: undefined
// b done
// a starting
// in b, foo: foo
// a done
// in a, setTimeout bar: 2
// 注意该例不能用const或let,否则报错:Cannot access 'bar' before initialization 暂时性死区
// export变量声明提升
高版本浏览器可以直接使用 es6 module
script
标签加type='module'
属性启动支持支持相对路径和绝对路径
html<script type="module"> import { getName } from 'utils.js'; // error import { getName } from './utils.js'; // right </script>
使用
nomodule
向下兼容html<script type="module" src="module.js"></script> <script nomodule src="fallback.js"></script>
因老版本不识别
type="module"
即不会执行module.js
,同时不识别nomodule
即忽略该属性 参考加载方式默认使用
defer
只执行一次
html<!-- 1.js 只会被加载执行一次--> <script type="module" src="1.js"></script> <script type="module" src="1.js"></script> <script type="module"> import './1.js'; </script> <!-- 普通JS 也只会被加载一次,但是会被执行多次--> <script src="2.js"></script> <script src="2.js"></script>
type="module"
默认不支持跨域,需要服务器设置cors
服务器必须要设置有效的
MIME types
:text/javascript
动态异步加载 import()
import()
返回一个promise对象
,可以用在任何地方,运行时加载- 主要用在
按需加载
及条件加载
- 使用 babel 编译时,需要添加
syntax-dynamic-import
插件
UMD 模块
实际上就是 amd/cmd + commonjs + 全局变量
这三种风格的结合,对当前运行环境的判断,如果是 Node
环境 就是使用 CommonJs
规范, 如果不是就判断是否为 AMD
环境, 最后导出全局变量。(AMD
)
javascript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? (module.exports = factory()) : typeof define === 'function' && define.amd ? define(factory) : (global.libName = factory());
})(this, function () {
'use strict';
});