一道代码题看 CommonJS 模块化规范

CommonJS

node.js中,使用 CommonJS 作为其模块化规范。

根据 CommonJS 模块化规范,一个单独的JS文件就是一个模块,每个模块是一个单独的作用域,在模块内部定义的变量,无法在其他模块中所读取。若想要在模块间进行通信,需使用模块的导出与导入语法:

  • 模块通过变量exports向外暴露API,注意,exports只能是一个对象,该对象的属性即为向外暴露的API
  • 在另一个模块中,通过全局函数require引入其他模块导出的exports对象。

基本语法

例如,a.js文件中导出对象exports,其中包含了nameadd属性,分别是文件中定义的常量。

const name = 'Jack';

const add = (a, b) => a + b;

module.exports = {
  name,
  add
};

在文件b.js中,通过全局函数require引入该导出的对象exports

const api = require('./a');

console.log(api);
// { name: 'Jack', add: [Function: add] }

注意,api这个可以随意取的。通常,也可以通过解构赋值的形式,按需进行导入:

const { name } = require('./a');

console.log(name);
// Jack

多次导入

接上述例子,若在b.js文件中,多次导入a模块,会怎么样呢?两次导入的是同一个对象吗?

const api1 = require('./a');
const api2 = require('./a');

console.log(api1 === api2);
// true

如上述代码所示,b.js中两次引入a模块,并判断两个导入的变量是否为相等。结果输出true,说明若多次引用,实际上导入的模块是同一个对象。

实际上,在第一次导入中,会执行要导入的文件,并在内存中缓存一个对象,其中exports就是要导入的对象。再次导入相同模块时,并不会再执行了,会直接从内存中取这个exports对象。

{
  id: '...', // 模块名
  exports: { ... }, // 模块输出的接口
  loaded: true, // 模块的脚本是否执行完毕
  ...
}

是否支持更改

导入模块,实际上就是执行一遍要导入的模块,然后将其输出的exports对象,作为require函数的返回值。实际上就是普通的赋值语句,若使用varlet进行声明,当然支持更改。

let api1 = require('./a');

api1 = {};

console.log(api1);
// {}

循环引用

若出现某模块被循环加载,只输出已经执行的部分,未执行的不会输出。

若在a.js中引入b.js,在b.js中引入a.js,形成循环引用,是否会报错呢?若不报错,运行的结果是怎么样的?

还是以代码举例比较清晰。如下代码所示,a模块引入b模块,b模块引入a模块。

// a.js
const b = require('./b');

console.log('b', b);

const name = 'Jack';
const age = 18;

module.exports = {
  name,
  age
};
// b.js
const a = require('./a');

console.log('a', a);

const id = '001';

module.exports = {
  id
};

运行node a.js,控制台输出如下:

a {}
b { id: '001' }

没有报错,正常运行。这是因为前面提到的原因:若出现某模块被循环加载,只输出已经执行的部分,未执行的不会输出。

简单分析一下:

  • 运行node a.js。首先进入到a模块,a模块一开始就导入b模块,那么就会先执行一遍b.js,然后返回b.js导出的exports对象给a模块中的常量b
  • 执行b.js。一开始又导入了a.js。但是a.js并未执行完毕,只会返回已执行的部分。由于a.js导出的是nameage,此时a.js只执行到第1行,所以实际上此时导入a.js,只会返回空对象,因为nameage还未执行到。所以require('./a')返回空对象,赋值给常量a,故打印出空对象
  • 接着继续执行b.js,将剩下的代码执行完毕,正常导出了exports对象,其中包含了id属性。
  • 执行完毕后,又回到了a.js的第一行中,require('./b')返回b导出的exports对象,包含了id属性。所以,下面能正常打印出{ id: '001' }
  • 接着在a.js中,将剩余的代码执行完毕。

思考题

a.jsb.jsc.js三个文件,执行node a.js,控制台将会输出什么?

// a.js
const b = require('./b');

console.log(exports.x);

exports.x = 'x';

require('./c');
// b.js

const a = require('./a');

console.log(a);

a.x = 'y';
// c.js
const a = require('./a');

console.log(a.x);

答案:

{}
y
x

以上为本人学习所得,若有不妥,欢迎批评指出!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火星飞鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值