在es6之前,社区制定了一些模块加载方案,主要有CommonJs和AMD两种,前者用于服务器,后者用于浏览器,所以,在es6语言标准的层面,实现了模块化功能,而且实现的非常简单,完全可以取代现有的CommonJs和AMD规范,成为浏览器和服务器通用的模块解决方案。
es模块化的思想家时尽量的静态化,使得编译时就能确定模块的依赖关系,以及输出和输入的变量。而CommonJs和AMD值观在运行时确定,比如CommonJS就是把一个模块执行得到所有对外暴露的属性,然会给一个对象,引用者指向对象。
CommonJs
Commonjs是服务器端模块的规范,而nodejs采用了这个规范,在Commonjs规范里,一个单独的文件就是一个模块。
CommonJS 主要针对服务端模块化方案,因为是同步加载的
特点
- 所有的代码运行在模块作用域下,不会污染全局作用域
- 模块可以多次加载,但是只会在第一次加载时和运行时加载一次,然后的运行结果就被缓存起来,以后再加载直接读取缓存中的模块,要想让模块再次运行,必须清除缓存
- 模块的加载顺序按照在其代码中出现的顺序
- CommonJs 的模块化就是把一个文件全部执行,把已经对外暴露的资源赋值给了modules.exports 对象,而后面又赋值给了引用者。
- commonJs 的模块化输出的是一个
commonjs 的引用模块是同步加载,等到模块内部的代码执行完毕之后返回给引用者
module对象
node中提供一个module构造函数,所有模块都是module的实例。每个模块内部都有一个module对象,代表当前模块
id 模块的标识符,通常带有绝对路径的模块文件名
- filename 模块的文件名,带有绝对路径
- loaded 返回一个bool,表示模块是否已经加载完成
- parent 返回一个对象,表示调用该模块的模块
- children 返回一个数组, 表示该模块要用到的其他模块
- exports 表示模块对外输出的是一个值的拷贝,一个值一旦导出,模块内部的变化就影响不到这个值了 。而后面再次引用是从缓存中获取
module.exports
module。exports 初始值是一个node提供的一个空对象,把module.exports 的引用赋值给了exports,所以exports指向的是module.exports的引用,在require的时候,获取的module.exports 对象。
而在使用模块导出的时候就像是….
// a.js
const objects = {};
objects.name = "变量";
objects.func = () => {};
// b.js
const obj = require("./a.js") // => obj == objects;
exports
快捷方式,为了使用方便,node为每个模块提供了一个exports变量,指向module.exports
//类似于
const exports = module.exports;
// 所以可以
exports.fn = () => {};
exports.vals = "变量';
但是不能直接覆盖exports对象,这样就等于对该对象重新赋值,而不是指向原有的module.exports
~举个栗子
// a.js
exports.a = function(){
console.log('a')
}
module.exports = {a: 2}
exports.a = 1 ;
// b.js
var x = require('./foo');
console.log(x.a) // a ==> 2
上面的中引用改成对象就相当于…
let origins = {};
let obj = origins;
origins = {
a: 9090,
b: 800
}
obj.a = 100;
console.log(cobjs, origins);
此时origins的地址被改变,obj始终指向的是origins的源地址,所以origins的任何更改始终和obj没有关系
CommonJs 的模块化就是把一个文件全部执行,把已经对外暴露的资源赋值给了modules.exports 对象,而后面又赋值给了引用者。
ESmodule
该模块化方案是在es6提出的,现在已经广泛支持,属于正规军
设计思想就是尽量的静态化,使得编译时就能确定模块的依赖关系。以及输出和输入的变量。
只有在使用的时候才会去接口中取值
CommonJs 输出的值是值的拷贝,一旦输出一个值,模块内部的变化就影响不到这个值。
ES6module,遇到加载命令时,不会去执行模块,而是生成一个动态的只读引用,等到需要的时候在去模块里面去取值,es6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块,比如…
// 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
特(优)点
- 不再需要UMD模块格式,将来服务器和浏览器都会支持。(正规军)
- 将来浏览器的新api就能用模块格式提供,不再必要做全局变量或者navigator对象的属性。(啥意思?)
- 不在需要命名空间,这些功能都可以使用包的概念来管理由包来提供
严格模式
- 变量必须声明后再使用。
- 函数的参数不能有同名属性
- 不能使用with语句
- 不能对只读属性赋值
- 不能使用前缀0标识八进制数,
- 不能删除不可删除的属性
- 不能使用delete prop,只能删除delete globle[prop]
- eval 不会在它的外层作用域引入变量
- eval和arguments不能被重新赋值
- argmenuts不会自动反应函数参数的变化
- 不能使用argumnets.callee && .caller
- 禁止this指向全局对象
- 不能使用fn.caller和fn.argumnets获取函数调用的堆栈
- 增加了保留字,(protected, static,interface)
默认导出
```javascript export default 10 ; // 默认导出一个10
export default () => {};
// 引用 import mod from “./xxx”;
当前模块就是一个函数,默认导出了一个函数
<a name="dILKx"></a>
### 具名导出
```javascript
export const site = "北京“
export const func = () => {};
// 引用
import { site, func} from "./xxx";
别名
export {
name as lastName
};
import { lastName as surname } from './profile';
复合用法
export { foo, bar } from 'mod';
// 等同于
import { foo, bar } from 'mod';
export { foo, boo};
// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';
// 默认接口的导出和导入
export { default } from 'foo';
// 具名接口改为默认接口
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
// 默认导出改为具名导出
export { default as es6 } from './someModule';
模块的整体加载
export function area(radius) { }
export function circumference(radius) { }
// 引入
imoprt * as mod from "./xxx"
此方式会把该模块所有的导出全局加载进来,即使没有使用
循环加载
循环加载指的是,a.js引用了b.js,而b.js又引用了a.js
在CommonJs中
首先require命令指向一个脚本,然会在内存中生成一个对象,以后再需要用到这个模块的时候,就会到exports属性上面取值,即使再次require,也不会执行该模块,而是到缓存中取值,也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存
CommonJs 模块的重要特性就是在加载时执行,既在require的时候,就会全部执行,一旦出现某个模块被循环加载,就只会输出已经执行的部分,还没执行的部分不会导出
~~ 举个栗子
// a.js
exports.done = "a.js 第一行输出的模块"
const b = require("./b.js");
console.log(`a.js 中, b.done = ${b.done}`);
exports.done = "a.js 第二次输出的值";
cosnole.log("a.js执行完毕");
// b.js
exports.done = "b.js 第一次输出";
const a = require("a.js");
console.log(`b.js 中, a.done = ${a.done}`);
exports.done = "b.js 第二次输出的值";
console.log("b.js 执行完毕");
// mian.js
const a = require("a.js");
const b = require("b.js");
console.log(`在main.js 中, a.done = ${a.done}; b.done=${b.done}`);
// 结果
b.js 中, a.done = a.js 第一行输出的模块
b.js 执行完毕
a.js 中, b.done = b.js 第二次输出的值
a.js执行完毕
在main.js 中, a.done = a.js 第二次输出的值; b.done=b.js 第二次输出的值
解释一哈:
在a.js 中,require执行了b.js 模块,而在a.js中只有第一行执行了,所有是”a.js 第一行输出的值”;
在b.js 中, 第一行执行了,而又去引用了a.js 此时a.js 只有第一执行所以done = “a.js 第一行输出的值”, 而接着往下执行,在第12行dnoe被覆盖掉done = “b.js 第二次输出的值”, b.js 执行完毕。
返回a.js 所以在a.js 中得到的 b.done = b.js 第二次输出的值, a.js 执行完毕
main.js 中 —> a.done = a.js 第二次输出的值; b.done=b.js 第二次输出的值
在ES module中
因为在这里模块是动态引用,静态编译,在运行前就以确定,变量不会被缓存,而是成为一个指向被加载模块的引用,
~~ 举个栗子
// a.js
import { foo } from "./b.js";
console.log(`a.js foo = ${foo}`);
export let site = "上海";
// b.js
import { site } from "./a.js";
console.log(`b.js site = ${site}`);
export let foo = "b.js 的北京"
// 结果
log -> b.js site = undefined
log -> a.js foo = "b.js 的北京";
解释一哈~:
a.js 第一行是加载b.js, 所以先执行b.js; 而b.js 的第一行又引用了a.js; 这时a.js 已经在执行中,所以不会重复执行;
而是继续执行b.js;而b.js 要打印site这个变量,而a.js 还没有执行我完毕,所以site = undefined;
后面b.js 继续执行,完毕之后回到a.js, 在b.js 已经执行完毕,在a.js 中可以拿到foo 所以后面 foo = “b.js 的北京”;
import - func
由于import 语句会被静态分析,先于模块内的其他模块执行,所以只能在顶部引入,不可出现在块级作用域内,而require方法是运行时加载模块,import语句显然不能对比。
所以引入了import 函数,完成动态加载
import 函数返回一个promise,可以用在任何地方,不仅仅是模块,可以实现动态加载;
于require的区别就是,require是同步加载, import是异步加载
浏览器中的模块
<script type="module" src="foo.js"></script>
在网页中插入一个模块 foo.js 加上了module 值,所以浏览器知道这是一个es6的模块
浏览器对于带type = module 的script, 都是异步加载外部模块,不会造成堵塞浏览器
在此
- 该脚本自动采用严格模块。
- 该脚本内部的顶层变量,都只在该脚本内部有效,外部不可见。
- 该脚本内部的顶层的
this
关键字,返回undefined
,而不是指向window
而在工程化中,es-module是静态加载是在编译时,比如说预编译阶段…
明显的区别
在es-module中
// 栗子
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// 在500毫秒后
foo // -> baz
// 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
export 语句导出的接口,与对应的值是动态绑定的关系,可以通过该接口,可以获取到模块内部实时的值,
与CommonJs 不同,该方式输出的值是值的缓存,不存在动态更新,
在CommonJs中
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
lib.js
模块加载以后,它的内部变化就影响不到输出的mod.counter
了。这是因为mod.counter
是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
输出的counter
属性实际上是一个取值器函数。现在再执行main.js
,就可以正确读取内部变量counter
的变动了
// es-module
// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
counter++;
return n == 0 || odd(n - 1);
}
// odd.js
import { even } from './even';
export function odd(n) {
return n != 0 && even(n - 1);
}
// CommonJs
// even.js
var odd = require('./odd');
var counter = 0;
exports.counter = counter;
exports.even = function(n) {
counter++;
return n == 0 || odd(n - 1);
}
// odd.js
var even = require('./even').even;
module.exports = function(n) {
return n != 0 && even(n - 1);
}
上面代码中在commonjs中根本无法执行,会报错,你猜…
AMD
amd 格式总体的目标是为了现在的开发者提供一个可用的模块化JavaScript的解决方案.
AMD 模块格式本身是一个关于如何定义模块的提案,在这种定义下模块和依赖项都能够异步的进行加载。
AMD 主要针对与浏览器的模块化方案。AMD通过