欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

CommonJs VS ES6 Module

程序员文章站 2022-03-07 16:40:55
CommonJSCommonJs 规范主要被用于 nodejs。module.exports/exportsnode 模块一般有两种导出方式:const a = 0;// 一个一个导出module.exports.a = a;// 一个一个导出exports.a = a;// 整体导出module.exports = { a };// 错误exports = { a };重点:exports默认指向module.exports。对exports重新赋值会导致export...

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 使用静态语法,且importexport只能出现在代码的顶层。

// 错误,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 所对应的内存。

关系图如下:

CommonJs VS ES6 Module

不难推断,对于 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

相关标签: javascript js