CommonJs VS ES6 Module
CommonJS
CommonJs 规范主要被用于 nodejs。
module.exports/exports
node 模块一般有两种导出方式:
const a = 0;
// 一个一个导出
module.exports.a = a;
// 一个一个导出
exports.a = a;
// 整体导出
module.exports = { a };
// 错误
exports = { a };
重点:exports
默认指向module.exports
。
对exports
重新赋值会导致exports
不再指向module.exports
。
对于 node 模块来说,其导出的都是对象。
// a.js
console.log(module.exports);
// b.js
const a = require("./a.js");
console.log(a);
node b.js
{}
{}
因为module.exports
默认为{}
,哪怕没有显式的导出,仍然获取到了默认的{}
。
注意:ES6 模块是截然不同的,必须显式的导出。
require
上文我们知道,node 模块导出的都是对象,所以导入 node 模块就是对一个对象进行取值。
//a.js
module.exports = { a: "a" };
// b.js
const obj = require("./a.js");
const a = obj.a;
const { a } = require("./a.js");
const a = require("./a.js").a;
注意:无论哪种方式,都是导入了完整的对象。
要点
node 模块只会在第一次加载时运行,之后加载都是直接获取缓存(导出对象)。
// a.js
console.log("a");
// b.js
require("./a.js");
// c.js
require("./a.js");
require("./b.js");
node c.js
a
虽然导入了两次 a.js,结果只有打印了一次a
。
node 模块的导出不是动态绑定的。
// a.js
let counter = 0;
function incCounter() {
counter++;
}
module.exports = { counter, incCounter };
// b.js
const { counter, incCounter } = require("./a.js");
console.log(counter);
incCounter();
console.log(counter);
node b.js
0
0
打印结果均为 0。
原因:
在 a.js 中,counter 变量存储在栈内存,执行 incCounter 函数,counter 变量值加 1 ,但是这和模块导出的对象压根没关系。
a.js 实际导出的module.exports
指向的是复杂对象{ counter, incCounter }
的地址,地址存储在栈内存,复杂对象存储在堆内存。
如果想要让 counter 能够响应变化,可以设置 counter 为函数。
// a.js
let counter = 0;
function incCounter() {
counter++;
}
module.exports = {
counter() {
return counter;
},
incCounter,
};
// b.js
const { counter, incCounter } = require("./a.js");
console.log(counter());
incCounter();
console.log(counter());
node b.js
0
1
ES6 Module
ES6 Module 规范致力于统一 JavaScript 各个领域中的模块系统。
注意:遵循 ES6 Module 规范的文件在 node 下执行要以.mjs 为文件后缀名。
export
export 主要语法如下:
// 方式一
const a = 0;
export { a };
// 方式二
export const a = 0;
// 重命名
const a = 0;
export { a as A };
// 错误,把`{}`当做了对象语法
export { a: 0 };
// 错误,接口不能为常量
const a = 0;
export a;
// 错误,接口不能为常量
export 0;
重点:export { a }
不是对象语法,下文的import
也是如此。
export default
export default
指定模块的默认导出,实际上就是设置一个名为default
的接口。
const a = 0;
export default a; // export { a as default };
import
模块导入与模块的导出相对应。
import { a } from "./a.mjs";
// 重命名
import { a as A } from "./a.mjs";
// 导入全部接口
import * as A from "./a.mjs";
// 导入默认接口
import A from "./a.mjs"; // import { default as A } from "./a.mjs";
注意:import
导入的是 constant variable,不能重新赋值。
import { a } from "./a.mjs";
a = 0; // TypeError: Assignment to constant variable.
import()
import()
用于异步加载,返回一个 Promise 对象。
本文主要介绍模块,语法内容不再过多阐述,可参考阮老师的文章。
要点
ES6 Module 使用静态语法,且import
、export
只能出现在代码的顶层。
// 错误,import、export 只能出现在代码的顶层
function test() {
export const a = 0;
}
// 错误,import 语句使用静态语法
const filter = true;
import filter ? a : A from './a.mjs'
// 错误,'a'为常量
import 'a' from './a.mjs'
ES6 Module 只会在第一次加载时运行。
// a.mjs
console.log("a");
// b.mjs
import "./a.mjs";
// c.mjs
import "./a.mjs";
import "./b.mjs";
node c.mjs
a
虽然导入了两次 a.mjs,但是只打印了一次 a。
ES6 Module 的数据是动态绑定的。
// a.mjs
let counter = 0;
function incCounter() {
counter++;
}
export { counter, incCounter };
// b.mjs
import { counter, incCounter } from "./a.mjs";
console.log(counter);
incCounter();
console.log(counter);
node b.mjs
0
1
a.mjs 中变量 counter 的变化映射到了 b.mjs。
原因:
ES6 Module 使用的是静态语法,所以模块的依赖关系在代码运行前就可以确定。
简单来说,就是在代码运行前,我们就知道了整个项目有哪些导入导出。
下面两段代码可以说明:
// a.mjs
console.log(0);
export const a = 0;
// b.mjs
import { A } from "./a.mjs";
运行 b.mjs,会报错且不会打印 0,说明代码运行前就确定需要导入的 A 接口不存在。
// a.mjs
console.log(0);
export { a };
// b.mjs
import { a } from "./a.mjs";
运行 b.mjs,会报错且不会打印 0,说明代码运行前就确定需要导出的 a 接口未定义。
模块的导出就是在栈上开辟了内存用于存放值(简单数据)或者地址(复杂数据),如 a.mjs,实际上就是在栈上开辟了两处内存,然后让接口(变量) counter 和 incCounter 指向这两处内存。
模块的导入就是设置变量指向对应接口,如 b.mjs,设置变量 counter 和 incCounter 指向 a.mjs 中的接口 counter 和 incCounter 所对应的内存。
关系图如下:
不难推断,对于 ES6 Module,模块中接口数据的变化可以映射到所有其它引用该接口的模块。
import 语句会被提升到代码顶部。
// a.mjs
console.log(0);
export const a = "a";
// b.mjs
console.log(a);
import { a } from "./a.mjs";
node b.mjs
0
a
虽然变量 a 在import { a } from "./a.mjs"
前就被使用,但是不会报错。
并且因为是第一次导入 a.mjs,所以执行了 a.mjs。
循环加载
本文就不对循环加载做详细分析了,大家可以参考阮老师的文章。
这里只做一个简单的总结:node 模块和 ES6 Module 在第一次导入模块时都会执行模块代码,唯一需要注意的点就是 ES6 Module 的 import 语句会被提升到代码顶部。
结语
本文地址:https://blog.csdn.net/XieJay97/article/details/107484344
下一篇: 简单演示通过模板元编程计算容器大小