当前位置: 移动技术网 > IT编程>开发语言>JavaScript > CommonJs VS ES6 Module

CommonJs VS ES6 Module

2020年07月22日  | 移动技术网IT编程  | 我要评论
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 所对应的内存。

关系图如下:

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

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网