一起学习NodeJs的模块化

栏目: Node.js · 发布时间: 5年前

内容简介:原文在介绍之前,我们可以先简单了解一下推荐文章:

原文 github 地址戳这里: github.com/wangkaiwd/n…

在介绍之前,我们可以先简单了解一下 javascript 的模块化历程

javascript 模块化

  • AMD : Asynchronous Module Definition
  • CMD : Common Module Definition
  • UMD : Universal Module Definition
  • CommonJS : Node 采用该模块化方式
  • ES6 模块化

推荐文章:

由于这里使用的是 Nodejs ,所以我们主要学习一些 CMD(commonJS) 的相关内容

NodeJs 中的模块化

requiremodule.exports 使用

NodeJs 中,我们会通过 module.exports 或者 exports 来导出一个 javascript 文件中定义的元素,然后通过 require 将导出元素进行引入:

// demo02.js
console.log('1');
module.exports = () => {
  console.log('Hi, I am module demo2');
};
console.log('2');

// demo01.js
console.log('before require');
const demo2 = require('./demo02');
console.log('after require');
demo2();
复制代码

接下来我们在当前目录中打开命令行窗口,输入 node demo02.js

before require
1
2
after require
Hi, I am module demo2
复制代码

所以,我们在通过require将一个模块导入的时候,不仅可以接收模块内部通过module.exports暴露的元素,还会执行相应模块内的js代码

接下来,我们在 demo01.js 中再加入以下代码:

const repeatDemo2 = require('./demo02');
repeatDemo2();
复制代码

执行后的输出结果如下:

before require
1
2
after require
Hi, I am module demo2
Hi, I am module demo2
复制代码

输出结果大概告诉我们这样一件事: 在首次引入某个模块的时候, NodeJs 会对模块内的代码进行缓存,而当我们再次引入该模块时,会通过缓存来读取导出的内容,模块内的代码并不会重新执行。

我们可以通过 require.cache 属性来看到 NodeJs 对模块的缓存:

// 在引入模块之前和之后分别输出require.cache
// demo03.js
console.log('before require');
console.log(require.cache);
const demo2 = require('./demo02');
console.log('after require');
console.log(require.cache);
复制代码

通过截图我们可以很明显的看出,在 require demo02 后缓存中多了一些内容:

一起学习NodeJs的模块化

在阅读完上边的代码之后,这里我们可以对 require 的功能进行一个小结:

  1. require 会引入一个模块中通过 module.exports 导出的元素
  2. require 首次引入模块过程中,会执行模块文件中的代码,并将模块文件进行缓存
  3. 当我们再次引入该模块的时候,会从缓存中读取该模块导出的元素,而不会再次运行该文件

exportsmodule.exports

我们先看一下 NodeJs 官方对 exports 的定义:

exports 变量是在模块的文件级作用域内可用的,且在模块执行之前赋值给 module.expors

这句话的大概意思是说: exports 并不是一个全局变量,只在模块文件内有效,并且在每个模块文件( js 文件)执行之前将 module.exports 的值赋值给 exports 。即相当于在每个 js 文件的开头执行了如下代码:

exports = module.exports
复制代码

这意味着 exportsmodule.exports 指向了同一片内存空间,当为 exports 或者 module.exports 重新赋值的时候,它们将不再指向同一个引用, 而我们 requie 引入的一直都是 module.exports 导出的内容

// demo04.js
// 本质上来讲:exports是module.exports的一个引用,它们指向同一片内存空间
// exports = module.exports
exports.a = 1;
module.exports = { b: 2 }; // 当引用发生变化的时候,exports不再是module.exports的快捷方式
复制代码

这时模块暴露出来的对象是 {b:2}

官方也对这种行为进行了假设实现:

function require(/* ... */) {
  // 一个全局的module对象
  const module = { exports: {} };
  // 这里自执行函数传参时进行了赋值: exports = module.exports
  ((module, exports) => {
    // 模块代码在这。在这个例子中,定义了一个函数。
    function someFunc() {}
    exports = someFunc;
    // 此时,exports 不再是一个 module.exports 的快捷方式,
    // 且这个模块依然导出一个空的默认对象。
    module.exports = someFunc;
    // 此时,该模块导出 someFunc,而不是默认对象。
  })(module, module.exports);
  // 最终导出的一直都是module.exports,只不过可以通过exports来更改它们的引用,间接的改变module.exports
  return module.exports;
}
复制代码

模块之间的循环引用

假设我们有这样一种场景: 模块 a.js 依赖于 b.js 中的某个方法,而模块 b.js 也同样依赖于 a.js 中的某个方法,这样的话会不会造成死循环呢?

笔者这里写了一个 demo 来重现这个问题,帮助我们更好的理解模块之间的相互引用:

// demo05.js
const demo6 = require('./demo06');
console.log('I am demo5', demo6);
module.exports = { demo5: 'demo5' };

// demo06.js
const demo5 = require('./demo05');
console.log('I am demo6', demo5);
module.exports = { demo6: 'demo6' };
复制代码

执行结果如下(我们可以先猜一下):

I am demo6 {}
I am demo5 { demo6: 'demo6' }
复制代码

所以我们可以得出以下执行过程:

  1. 命令行执行 node demo05.js
  2. 首先引入模块 demo06.js ,并且执行 demo06.js ,通过变量 demo6 来接收模块 demo06.js 通过 module.exports 导出的对象
  3. 在执行 demo06.js 的过程中,又引入了 demo05.js ,而由于 demo05.js 已经执行了一部分,由于缓存原因,并不会重新执行,此时 demo05.js 中的 module.exports 还是初始值 {} 。所以变量 demo5{}
  4. demo05.js 在引入 demo06.js 后继续执行后续代码

可以看出 nodeJs 对于模块之间的递归引用进行了优化,并不会引发死循环,但是需要注意的是在引入的时候要注意代码的执行顺序,否则可能会取不到对应的变量。

到这里,小伙伴在 NodeJs 中使用 require 进行引入以及通过 module.exports 来导出文件时的执行逻辑有了更清晰的认识呢?


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

iOS软件开发揭密

iOS软件开发揭密

虞斌 / 电子工业出版社 / 2011-5-1 / 79.00元

本书以严密的体系性提供了iPhone和iPad软件开发从入门到专家的系统性知识,并提供来源于真实项目的可重用商业代码。书中的每个实例都是项目经验的提炼,深入浅出地讲解iPhone和iPad软件开发的核心技术要点,基本涵盖了iOS软件开发在真实商业项目中所需要的所有主题,并将实例介绍的技术深度和超值的实用性结合在一起,成为本书的特色。 随书附赠的光盘中包含了书中大量案例的完整工程源代码,可以让......一起来看看 《iOS软件开发揭密》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具